MockBean在RESTful服务中很奇怪。

huangapple go评论76阅读模式
英文:

MockBean is strange in restful services

问题

I've made a REST controller that calls a service class. The UnitServiceImpl class implements the UnitService interface and contains methods for adding, getting, and retrieving units. It also handles the EntityNotFoundException by throwing it in case a unit is not found.

The EntityNotFoundException is handled by the ExceptionHandlingController, which is a controller advice class that extends ResponseEntityExceptionHandler. It uses @ExceptionHandler to handle runtime exceptions, and if the exception is an EntityNotFoundException, it returns a NOT_FOUND status.

The UnitController is another REST controller that calls the methods from the UnitServiceImpl class. It handles POST and GET requests for units.

You have created unit tests for the UnitController class using @AutoConfigureMockMvc and @SpringBootTest. However, you encountered issues with the tests failing. Specifically, the testGetAllUnits test fails with a "Content type not set" error, and the testUnitNotFound test fails with a "Status expected:<404> but was:<201>" error. These issues seem to be related to the use of @MockBean for UnitService.

In your update, you mentioned a similar problem with the testAddUnit test, where you mock the unitService.addUnit method.

The issue with these tests failing when you use @MockBean for UnitService is likely due to the mocked service behavior not being properly configured. When you use @MockBean, you need to specify the behavior of the mocked methods to return the expected values or throw the expected exceptions.

To fix these issues, make sure that you properly configure the behavior of the UnitService mock in your test methods using given(...).willReturn(...) or given(...).willThrow(...). This will ensure that the service methods behave as expected during testing.

Here's an example of how to configure the unitService.addUnit mock for the testAddUnit test:

@Test
void testAddUnit() throws Exception {
    Unit unit = new Unit();
    unit.setId(1);
    unit.setUnitName("TestUnit");

    given(unitService.addUnit("TestUnit")).willReturn(unit); // Configure the mock behavior

    mockMvc.perform(post("/unit").param("unit_name", "TestUnit"))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.unitName").value("TestUnit"))
            .andExpect(jsonPath("$.id").value(1));
}

Make sure to similarly configure the behavior of the unitRepository mock and other mocks in your tests to ensure that they behave as expected during testing.

英文:

I've made rest controller, that calls @service class:

@Service
public class UnitServiceImpl extends HttpRequestServiceImpl implements UnitService {

    @Override
    public Unit addUnit(String unitName) {
        final Unit unit = new Unit();
        unit.setUnitName(unitName);
        return unitRepository.save(unit);
    }
    @Override
    public Unit getUnit(int id) {
        final Unit unit = unitRepository.findById(id);
        if (unit == null) {
            throw new EntityNotFoundException(&quot;Unit is not found&quot;);
        }
        return unit;
    }

    @Override
    public Iterable&lt;Unit&gt; getAllUnits() {
        return unitRepository.findAll();
    }
}

EnityNotFoundException is handled by ExceptionHandlingController:

@RestController
@ControllerAdvice
public class ExceptionHandlingController extends ResponseEntityExceptionHandler {

    @ExceptionHandler({RuntimeException.class})
    public final ResponseEntity&lt;ErrorDetails&gt; handleRuntimeException(RuntimeException ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
                request.getDescription(false));
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        if (ex.getClass() == EntityNotFoundException.class) {
            httpStatus = HttpStatus.NOT_FOUND;
        }
        return new ResponseEntity&lt;&gt;(errorDetails, httpStatus);
    }
}

Unit controller just calls the getUnit:

@RestController
public class UnitController {
    private final UnitService managementService;


    @PostMapping(value = &quot;/unit&quot;, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity&lt;Unit&gt; addUnit(HttpServletRequest request) throws FieldsIsAbsentException {
        final String unitName = managementService.getParameter(request, &quot;unit_name&quot;);

        final Unit unit = managementService.addUnit(unitName);
        return new ResponseEntity&lt;&gt;(unit, HttpStatus.CREATED);
    }
    public UnitController(UnitService managementService) {
        this.managementService = managementService;
    }

    @GetMapping(value = &quot;/unit&quot;, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity&lt;Iterable&lt;Unit&gt;&gt; getAllUnits() {
        final Iterable&lt;Unit&gt; allUnits = managementService.getAllUnits();
        return new ResponseEntity&lt;&gt;(allUnits, HttpStatus.OK);
    }

    @GetMapping(value = &quot;/unit/{id}&quot;, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity&lt;Unit&gt; getUnitById(@PathVariable(&quot;id&quot;) int id) {
        final Unit unit = managementService.getUnit(id);
        return new ResponseEntity&lt;&gt;(unit, HttpStatus.CREATED);
    }
}

Now I need to test them, and created unit test method, that must to check on 404 error:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration
class UnitControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    UnitService unitService;

    @MockBean
    UnitRepository unitRepository;

    @Autowired
    private UnitController unitController;

    private List&lt;Unit&gt; units;

    @Before
    public void initUnits() {
        units = new ArrayList&lt;&gt;();
        Unit unitWithName = new Unit();
        unitWithName.setId(1);
        unitWithName.setUnitName(&quot;NameUnit&quot;);
        units.add(unitWithName);

        Unit unitWithoutName = new Unit();
        unitWithoutName.setId(2);
        units.add(unitWithoutName);
    }

    @Test
    void contextLoads() {
        Assert.assertNotNull(unitController);
    }

    @Test
    void testGetAllUnits() throws Exception {
        given(this.unitService.getAllUnits()).willReturn(units);
        mockMvc.perform(get(&quot;/unit&quot;))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON));
    }

    @Test
    void testUnitNotFound() throws Exception {
        int id = -1;
        given(this.unitRepository.findById(id)).willReturn(null);
        mockMvc.perform(get(&quot;/unit/-1&quot;))
                .andExpect(status().isNotFound())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON));
    }
}

When I run tests, testGetAllUnits fails:

java.lang.AssertionError: Content type not set

and testUnitNotFound fails with error:

java.lang.AssertionError: Status expected:&lt;404&gt; but was:&lt;201&gt;

But when I remove

@MockBean
UnitService unitService;

It will be working. What the problem?


UPDATE:
I have the similar problem now. This code inserts into database info about unit. But I made mock for the method.

    @Test
    void testAddUnit() throws Exception {
        Unit unit = new Unit();
        unit.setId(1);
        unit.setUnitName(&quot;TestUnit&quot;);

        given(unitService.addUnit(&quot;TestUnit&quot;)).willReturn(unit);
        mockMvc.perform(post(&quot;/unit&quot;).param(&quot;unit_name&quot;, &quot;TestUnit&quot;))
                .andExpect(status().isCreated())
                .andExpect(jsonPath(&quot;$.unitName&quot;).value(&quot;TestUnit&quot;))
                .andExpect(jsonPath(&quot;$.id&quot;).value(1));
    }

答案1

得分: 1

你正在嘲笑错误的Bean。引发异常的Bean是服务Bean,因此模拟它。

@Test
void testUnitNotFound() throws Exception {
    int id = -1;
    given(this.service.getUnit(id)).willThrow(new EntityNotFoundException("Unit is not found"));
    mockMvc.perform(get("/unit/-1"))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
英文:

You're mocking the wrong bean. The bean throwing the exception is the service bean, so mock that.

@Test
void testUnitNotFound() throws Exception {
    int id = -1;
    given(this.service.getUnit(id)).willThrow(new EntityNotFoundException(&quot;Unit is not found&quot;));
    mockMvc.perform(get(&quot;/unit/-1&quot;))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON));
}

答案2

得分: 0

testUnitNotFound() 测试无法正常工作的问题是,您期望在一个被模拟的服务内部发生的事情,而该服务也被模拟。

如果服务被模拟了,那么不会调用任何实现。只会返回默认值,即null。因此,不会像预期的那样引发异常...

如果您希望大部分服务被模拟,但其余部分使用其原始实现,那么您应该将:

@MockBean
UnitService unitService;

更改为

@SpyBean
UnitService unitService;
英文:

The problem with the testUnitNotFound() test not working is that you are expecting something from the mocked repository to happen inside a service which is also mocked.

If the service is mocked, then no implementation is invoked. Only a default value is returned which is null. And therefore no exception is thrown as expected...

If you want to have the flexibility of having most of the service mocked but having rest of them having their original implementations called, then you should change the:

@MockBean
UnitService unitService;

into

@SpyBean
UnitService unitService;

huangapple
  • 本文由 发表于 2020年8月6日 16:28:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/63279673.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定