获取 NestedServletException:请求处理失败;嵌套异常是 java.lang.IllegalStateException:映射的处理程序方法不明确

Getting NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: Ambiguous handler methods mapped

我想对我的小型应用程序进行 JUnit 5 集成测试。

问题发生在使用 MockMvc 使用 @PathVariable 发送 GET 请求的测试中。

我的控制器看起来像:

@RestController
@RequestMapping("/device")
public class DeviceController {

  private DeviceService deviceService;

  public DeviceController(DeviceService deviceService) {
    this.deviceService = deviceService;
  }

  @GetMapping
  public ResponseEntity<Set<DeviceDto>> findAllDevices() {
    return ResponseEntity.ok(deviceService.findAllDevices());
  }

  @RequestMapping(method = RequestMethod.GET, value = "/{id}")
  public ResponseEntity<Optional<DeviceDto>> findDeviceById(
      @PathVariable(value = "id") String id) {
    return ResponseEntity.ok(deviceService.findDeviceById(id));
  }

  @GetMapping("/{vendor}")
  public ResponseEntity<Set<DeviceDto>> findDevicesByVendor(@PathVariable String vendor) {
    return ResponseEntity.ok(deviceService.findAllDevicesByVendor(vendor));
  }

  @GetMapping("/{model}")
  public ResponseEntity<Set<DeviceDto>> findDevicesByModel(@PathVariable String model) {
    return ResponseEntity.ok(deviceService.findAllDevicesByModel(model));
  }

  @GetMapping("/preview")
  public ResponseEntity<Set<DeviceDtoPreview>> findDevicesWithIpAndSubnetMask() {
    return ResponseEntity.ok(deviceService.findAllDevicesForPreview());
  }
}

MongoDB 实体看起来像:

@Document
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class Device {

  @Id
  @GeneratedValue(generator = "uuid")
  public String id;

  @Field("manufacturer")
  public String vendor;
  @Field("model")
  public String model;
  @Field("serialNumber")
  public String serNum;
  @Field("macAddress")
  private String mac;
  @Field("ip")
  private String ip;
  @Field("subnetMask")
  private String netMask;
}

存储库:

public interface DeviceRepository extends MongoRepository<Device, String> {

  Set<Device> findAllByVendor(String vendor);

  Set<Device> findAllByModel(String model);

}

服务方式:


public Optional<DeviceDto> findDeviceById(final String id) {
    return deviceRepository
        .findById(id)
        .map(DeviceMapper.MAPPER::deviceToDeviceDto);
  }

  public Set<DeviceDto> findAllDevicesByVendor(final String vendor) {
    return deviceRepository.findAllByVendor(vendor)
        .stream()
        .map(DeviceMapper.MAPPER::deviceToDeviceDto)
        .collect(Collectors.toSet());
  }

  public Set<DeviceDto> findAllDevicesByModel(final String model) {
    return deviceRepository.findAllByModel(model)
        .stream()
        .map(DeviceMapper.MAPPER::deviceToDeviceDto)
        .collect(Collectors.toSet());
  }

我的 ControllerTest 看起来:

package com.interview.exercise.controller;

import static org.mockito.BDDMockito.given;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = DeviceController.class)
class DeviceControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private DeviceRepository deviceRepository;

  @MockBean
  private DeviceService deviceService;

  @Test
  void addDevice() {
  }

  @Test
  void findAllDevicesOnGetRequest() throws Exception {
    var device1 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.101 ")
        .netMask("255.255.0.1 ").model("model1").vendor("vendor1").build();

    var device2 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.102 ")
        .netMask("255.255.0.2").model("model2").vendor("vendor2").build();

    var device3 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.103 ")
        .netMask("255.255.0.3 ").model("model3").vendor("vendor3").build();
    var devices = Set.of(device1, device2, device3);
    given(deviceService.findAllDevices()).willReturn(devices);

    ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/device")
        .contentType(
            MediaType.APPLICATION_JSON))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$",
            Matchers.hasSize(3)));

  }

  @Test
  void findDeviceByIdOnGetRequest() throws Exception {
    var device = DeviceDto.builder().id("1").ip("192.168.0.101")
        .netMask("255.255.0.1 ").model("model1").vendor("vendor1").build();

    given(deviceService.findDeviceById("1")).willReturn(Optional.of(device));
    ResultActions resultMatchers = mockMvc.perform(MockMvcRequestBuilders
        .get("/device/{id}", "1")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$.model", Matchers.equalTo(device.getModel())));

  }

  @Test
  void findDevicesByVendorOnGetRequest() throws Exception {

    var device1 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.101 ")
        .netMask("255.255.0.1 ").model("model1").vendor("vendor1").build();

    var device2 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.102 ")
        .netMask("255.255.0.2").model("model2").vendor("vendor2").build();

    var device3 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.103 ")
        .netMask("255.255.0.3 ").model("model3").vendor("vendor3").build();
    var devices = Set.of(device1, device2, device3);
    given(deviceService.findAllDevicesByVendor("vendor3")).willReturn(devices);

    ResultActions resultActions = mockMvc
        .perform(MockMvcRequestBuilders
            .get("/device/{vendor}", "vendor3")
            .contentType(MediaType.APPLICATION_JSON))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$",
            Matchers.hasSize(1)));

  }

  @Test
  void findDevicesWithIpAndSubnetMaskOnGetRequest() throws Exception {
    var device1 = DeviceDtoPreview.builder().id(UUID.randomUUID().toString()).ip("192.168.0.101 ")
        .netMask("255.255.0.1 ").build();

    var device2 = DeviceDtoPreview.builder().id(UUID.randomUUID().toString()).ip("192.168.0.102 ")
        .netMask("255.255.0.2").build();

    var device3 = DeviceDtoPreview.builder().id(UUID.randomUUID().toString()).ip("192.168.0.103 ")
        .netMask("255.255.0.3 ").build();

    var device4 = DeviceDtoPreview.builder().id(UUID.randomUUID().toString()).ip("192.168.0.103 ")
        .netMask("255.255.0.3 ").build();
    var devices = Set.of(device1, device2, device3, device4);
    given(deviceService.findAllDevicesForPreview()).willReturn(devices);

    ResultActions resultActions = mockMvc
        .perform(MockMvcRequestBuilders.get("/device/preview").contentType(
            MediaType.APPLICATION_JSON))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$",
            Matchers.hasSize(4)));
  }

  @Test
  void findDevicesByModelOnGetRequest() throws Exception {
    var device1 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.101 ")
        .netMask("255.255.0.1 ").model("model1").vendor("vendor1").build();

    var device2 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.102 ")
        .netMask("255.255.0.2").model("model2").vendor("vendor2").build();

    var device3 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.103 ")
        .netMask("255.255.0.3 ").model("model3").vendor("vendor3").build();

    var device4 = DeviceDto.builder().id(UUID.randomUUID().toString()).ip("192.168.0.103 ")
        .netMask("255.255.0.3 ").model("model2").vendor("vendor4").build();
    var devices = Set.of(device1, device2, device3, device4);
    given(deviceService.findAllDevicesByModel("model2")).willReturn(devices);

    ResultActions resultActions = mockMvc
        .perform(MockMvcRequestBuilders.get("/device/{model}", "model2").contentType(
            MediaType.APPLICATION_JSON))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$",
            Matchers.hasSize(4)));

  }

}

没有 @PathVariable 的测试通过了,但是包含 @PathVariable 的测试出现了问题。我试图以不同的方式使用@RequestMapping,但它仍然产生相同的错误。

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: Ambiguous handler methods mapped for '/device/model2': {public org.springframework.http.ResponseEntity com.interview.exercise.controller.DeviceController.findDeviceById(java.lang.String), public org.springframework.http.ResponseEntity com.interview.exercise.controller.DeviceController.findDevicesByModel(java.lang.String)}

我非常确信,过去我创建的测试是相似的并且一切正常,但现在我无法在我的集成测试中使用 GET 请求达到目标。我将不胜感激如何修复我的 mockMvc.perform() 方法以达到理想效果的建议。

我不相信Spring能够区分@GetMapping("/{vendor}")@GetMapping("/{model}")。两者都有 @PathVariable 类型(字符串)和基本路径 /device.

为了帮助 Spring 正确地将请求映射到控制器方法,请在控制器中尝试这样的操作:

 @GetMapping("vendor/{vendorID}")
  public ResponseEntity<Set<DeviceDto>> findDevicesByVendor(@PathVariable String vendorID) {
    return ResponseEntity.ok(deviceService.findAllDevicesByVendor(vendorID));
  }

  @GetMapping("model/{modelID}")
  public ResponseEntity<Set<DeviceDto>> findDevicesByModel(@PathVariable String modelID) {
    return ResponseEntity.ok(deviceService.findAllDevicesByModel(modelID));
  }