The behavior that I am seeing is a NullPointerException in the second test when the mocked restTemplate
called. That pointed to a problem in the resetting of the mock. What surprised me is the fix ( that made both tests pass).
Modifying the code from
@MockBean private RestTemplate restTemplate;
to
@MockBean(reset = MockReset.NONE) private RestTemplate restTemplate;
fixed the issue. A few questions here:
Hopefully, I've provided enough context to answer the question.
I've created a simplified example of what I'm seeing: Test configuration:
@Profile("test")
@Configuration
public class TestConfiguration {
@Bean
@Primary
public ObjectNode getWeatherService(RestTemplate restTemplate) {
return new WeatherServiceImpl(restTemplate);
}
}
The test:
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureMockMvc
class SamTest {
@Autowired private MockMvc mockMvc;
@MockBean private RestTemplate restTemplate;
/*
Works:
@MockBean(reset = MockReset.NONE) private RestTemplate restTemplate;
Fails:
@MockBean(reset = MockReset.BEFORE) private RestTemplate restTemplate;
@MockBean(reset = MockReset.AFTER) private RestTemplate restTemplate;
*/
@Test
public void testOne() throws Exception {
Mockito.when(restTemplate.getForEntity("http://some.weather.api", ObjectNode.class))
.thenReturn(new ResponseEntity("{\"weather\" : \"rainy\"}", HttpStatus.OK));
// Makes call to standard @RestController with a @GetMapping
// Call to external API is contained in @Service class.
// Currently controller just passes through the json from the underlying service call.
this.mockMvc.perform(
get("/weather/check").
contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(status().isOk());
}
@Test
public void testTwo() throws Exception {
Mockito.when(restTemplate.getForEntity("http://some.weather.api", ObjectNode.class))
.thenReturn(new ResponseEntity("{\"error\" : \"bandwidth\"}", HttpStatus.BANDWIDTH_LIMIT_EXCEEDED));
this.mockMvc.perform(
get("/weather/check").
contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(status().is5xxServerError());
}
}
The service:
@Service
public class WeatherServiceImpl implements WeatherService {
private final RestTemplate restTemplate;
@Autowired
public WeatherServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public ObjectNode retrieve(URI uri) {
ResponseEntity<ObjectNode> response = restTemplate.getForEntity(uri, ObjectNode.class);
return response.getBody();
}
}
There is a misunderstanding about the @MockBean
default behaviour:
Why didn't the default @MockBean behavior of MockReset.RESET work? Is there something wrong with how I have set up my test such that default MockReset.RESET was failing?
From the MockBean.reset
method documentation:
The reset mode to apply to the mock bean. The default is MockReset.AFTER meaning that mocks are automatically reset after each test method is invoked.
So your MockBean
will be reset and unregistered from the application context after your first testcase execution and then your second testcase will find it null, while it will not happen in case of @MockBean(reset = MockReset.NONE)
as you have done.