英文:
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("Unit is not found");
}
return unit;
}
@Override
public Iterable<Unit> getAllUnits() {
return unitRepository.findAll();
}
}
EnityNotFoundException is handled by ExceptionHandlingController:
@RestController
@ControllerAdvice
public class ExceptionHandlingController extends ResponseEntityExceptionHandler {
@ExceptionHandler({RuntimeException.class})
public final ResponseEntity<ErrorDetails> 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<>(errorDetails, httpStatus);
}
}
Unit controller just calls the getUnit:
@RestController
public class UnitController {
private final UnitService managementService;
@PostMapping(value = "/unit", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Unit> addUnit(HttpServletRequest request) throws FieldsIsAbsentException {
final String unitName = managementService.getParameter(request, "unit_name");
final Unit unit = managementService.addUnit(unitName);
return new ResponseEntity<>(unit, HttpStatus.CREATED);
}
public UnitController(UnitService managementService) {
this.managementService = managementService;
}
@GetMapping(value = "/unit", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Iterable<Unit>> getAllUnits() {
final Iterable<Unit> allUnits = managementService.getAllUnits();
return new ResponseEntity<>(allUnits, HttpStatus.OK);
}
@GetMapping(value = "/unit/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Unit> getUnitById(@PathVariable("id") int id) {
final Unit unit = managementService.getUnit(id);
return new ResponseEntity<>(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<Unit> units;
@Before
public void initUnits() {
units = new ArrayList<>();
Unit unitWithName = new Unit();
unitWithName.setId(1);
unitWithName.setUnitName("NameUnit");
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("/unit"))
.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("/unit/-1"))
.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:<404> but was:<201>
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("TestUnit");
given(unitService.addUnit("TestUnit")).willReturn(unit);
mockMvc.perform(post("/unit").param("unit_name", "TestUnit"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.unitName").value("TestUnit"))
.andExpect(jsonPath("$.id").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("Unit is not found"));
mockMvc.perform(get("/unit/-1"))
.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;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论