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:
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?
Is there something wrong with the test config class?
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.
Related
I'm trying using #WebMvcTest and mock my service with #MockBean injecting restTemplate var to be mocked ( junit5).
How to use a bean configuration in service mocked and how to mock restTemplate var inside service mocked?
I need to qualifier restTemplate from service with configuration has already created.
Bean Configuration class
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
Service class
#Service
public class MyService {
// restTemplate is coming null on tests
#Autowired
private RestTemplate restTemplate;
public ResponseEntity<Object> useRestTemplate() {
return restTemplate.exchange(
"url",
HttpMethod.POST,
new HttpEntity<>("..."),
Object.class);
}
}
Test class
#WebMvcTest(controllers = MyController.class)
class MyControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MyService myService;
#MockBean
private RestTemplate restTemplate;
#Test
void test() throws Exception{
when(gatewayRestService.useRestTemplate()).thenCallRealMethod();
when(
restTemplate.exchange(
anySring(),
eq(HttpMethod.POST),
any(HttpEntity.class),
eq(Object.class)
)
).thenReturn(ResponseEntity.ok().body("..."));
mockMvc.perform(
post("/path")
.content("...")
.header("Content-Type", "application/json")
)
.andExpect(status().isOk() );
}
}
I have tried using #Import(RestTemplateConfig.class) on MyControllerTest but no success, restTemplate keep going null on tests in service
Why would you want to mock RestTemplate? #WebMvcTest is used to create a Spring MVC test that focuses only on Spring MVC components, meaning your MyController only. You should only bother mocking RestTemplate if you want to unit test MyService, which is not the case.
Having said that, you only need to mock MyService as follows:
#WebMvcTest(controllers = MyController.class)
class MyControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MyService myService;
#Test
void test() throws Exception {
when(myService.useRestTemplate()).thenReturn(ResponseEntity.ok().body("..."));
mockMvc.perform(
post("/path")
.content("...")
.header("Content-Type", "application/json")
)
.andExpect(status().isOk() );
}
}
I have one service called PostService, which has instance variable called connectionManager Autowired.
Inside this connectionManager there is a one instance variable called restTemplate Autowired.
And in the configuration java file, object of RestTemplate is created with some logic.
When I write a test case around it, and create a MockRestServiceServer with an expected URL and post method, and in return expect a response with some body. and when I execute a test case I don't get mocked response from this mockedRestServiceServer.
As much, I can sense, this is because during test execution, a real object of rest template is created and mock server is not used to send mocked response.
can someone help me, how to overcome from this ?
class PostServie {
#Autowired
private ConnectionManager connectionManager;
public void postMessage(String msg) {
// some logic
}
}
#Component
class ConnectionManager {
#Autowired
private RestTemplate restTemplate;
public String getToken(){
ResponseEntity<String> response = this.restTemplate.postForEntity(url, request, String.class);
//returns response.body() in string format
}
}
#Configuration
class Configuration {
#Bean
public RestTemplate getRestTemplate(){
// some logic and returns object of RestTemplate
}
}
#RunWith(SpringRunner.class)
class PostServiceTest {
#Autowired
private PostMessageService postMessageService;
#Resource(name="authServerRestTemplet")
private RestTemplate authServerRestTemplet;
private MockRestServiceServer mockedAuthServerRestTemplet;
private final String requestToAuthServer ="grant_type=client_credentials&client_id=ceapiClientId";
#Before
public void setUp() {
mockedAuthServerRestTemplet =
MockRestServiceServer.createServer(authServerRestTemplet);
}
#Test
public void postServeiceSuccess () {
mockedAuthServerRestTemplet.expect(requestTo(ACCESS_TOKEN_URI)).andExpect(content().string(requestToAuthServer)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess("{abc : 'abc'}", MediaType.APPLICATION_JSON));
postMessageService.postMessage(jsonMessage);
}
}
}
You're creating an instance of RestTemplate but not using it. What is MockRestServiceServer doing?
I can also see a wrong utilisation of #Resource and #Autowired, which you shouldn't specify in your test. Try use #MockBean.
Autowiring works so that the resource you create in your test is injected in the real class, not the other way round.
I'll just add some sample code here, which might help you achieve your goal.
#RunWith(SpringRunner.class)
#WebMvcTest
public abstract class AbstractControllerTest {
#Autowired
protected MockMvc mockMvc;
#MockBean
protected Service service;
#Before
public void setUp() {
Mockito.reset(service);
}
}
and then your test
public class YourTest extends AbstractControllerTest {
#Test
public void shouldDoSomething() throws Exception {
// given
List<someStuff> stuff = new ArrayList<>();
stuff.add(new Whatever("content", "John Smith");
// when - service is in the abstract controller!
when(service.getSomething()).thenReturn(stuff);
// then
mockMvc.perform(get("/posts/1/whatever").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content", is("content")))
.andExpect(jsonPath("$[0].author", is("John Smith"))));
}
When writing an integration test for a Spring Boot application (Service A) that uses a RestTemplate (and Ribbon under the hood) and Eureka to resolve a Service B dependency, I get a "No instances available" exception when calling the Service A.
I try to mock the Service B away via WireMock, but I don't even get to the WireMock server. It seems like the RestTemplate tries to fetch the Service instance from Eureka, which doesn't run in my test. It is disabled via properties.
Service A calls Service B.
Service discovery is done via RestTemplate, Ribbon and Eureka.
Does anyone have a working example that includes Spring, Eureka and WireMock?
I faced the same problem yesterday and for the sake of completeness here is my solution:
This is my "live" configuration under src/main/java/.../config:
//the regular configuration not active with test profile
#Configuration
#Profile("!test")
public class WebConfig {
#LoadBalanced
#Bean
RestTemplate restTemplate() {
//you can use your regular rest template here.
//This one adds a X-TRACE-ID header from the MDC to the call.
return TraceableRestTemplate.create();
}
}
I added this configuration to the test folder src/main/test/java/.../config:
//the test configuration
#Configuration
#Profile("test")
public class WebConfig {
#Bean
RestTemplate restTemplate() {
return TraceableRestTemplate.create();
}
}
In the test case, I activated profile test:
//...
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ServerCallTest {
#Autowired
private IBusiness biz;
#Autowired
private RestTemplate restTemplate;
private ClientHttpRequestFactory originalClientHttpRequestFactory;
#Before
public void setUp() {
originalClientHttpRequestFactory = restTemplate.getRequestFactory();
}
#After
public void tearDown() {
restTemplate.setRequestFactory(originalClientHttpRequestFactory);
}
#Test
public void fetchAllEntries() throws BookListException {
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer
.andExpect(method(HttpMethod.GET))
.andExpect(header("Accept", "application/json"))
.andExpect(requestTo(endsWith("/list/entries/")))
.andRespond(withSuccess("your-payload-here", MediaType.APPLICATION_JSON));
MyData data = biz.getData();
//do your asserts
}
}
This is what I done in my project:
Somewhere in your project configuration:
#LoadBalanced
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.build();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
return restTemplate;
}
#Bean
public SomeRestClass someRestClass () {
SomeRestClass someRestClass = new SomeRestClass (environment.getProperty("someservice.uri"), restTemplate(new RestTemplateBuilder()));
return parameterRest;
}
And SomeRestClass:
public class SomeRestClass {
private final RestTemplate restTemplate;
private final String someServiceUri;
public LocationRest(String someServiceUri, RestTemplate restTemplate) {
this.someServiceUri= someServiceUri;
this.restTemplate = restTemplate;
}
public String getSomeServiceUri() {
return locationUri;
}
public SomeObject getSomeObjectViaRest() {
//making rest service call
}
}
And Test class for SomeRestClass:
#RunWith(SpringRunner.class)
#RestClientTest(SomeRestClass.class)
public class SomeRestClassTest {
#Autowired
private SomeRestClass someRestClass;
#Autowired
private MockRestServiceServer server;
#Test
public void getSomeObjectViaRestTest() throws JsonProcessingException {
SomeResponseObject resObject = new SomeResponseObject();
ObjectMapper objectMapper = new ObjectMapper();
String responseString = objectMapper.writeValueAsString(resObject);
server.expect(requestTo(locationRest.getSomeServiceUri() + "/some-end-point?someParam=someParam")).andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).body(responseString));
someRestClass.getSomeObjectViaRest();
}
}
Note: I diasbled eureka client because otherwise you have to mock a eureka server. So I added eureka.client.enabled=false in test application.properties
Hopefully this can help someone. I was getting the same error with Ribbon, but without Eureka.
What helped me was
1) Upgrading to the latest version of WireMock (2.21) in my case
2) Adding a wiremock rule stub for url "/" to answer Ribbon's ping
If you are using Eureka just bypass in test/application.properties using eureka.client.enabled=false
#RunWith(MockitoJUnitRunner.class)
public class FeatureFlipperManagerTest {
#Autowired
RestTemplate restTemplate = new RestTemplate();
#Autowired
Service service = new Service();
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
#Test
public void test() throws Exception {
mockServer.expect(requestTo(Mockito.anyString()))
.andRespond(withSuccess("{\"enabled\":true}", MediaType.APPLICATION_JSON));
boolean res = service.isEnabled("xxx");
mockServer.verify();
Assert.assertEquals(true, res);
}
}
I have MockRestServiceServer to mock restTemplete in a service. But it always fail. it shows the error as java.lang.AssertionError: Further request(s) expected
0 out of 1 were executed. Any one could let me know where I did not do it right.
The service itself will looks as this:
public class Service{
public boolean isEnabled(String xxx) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity("someurl",String.class);
if(...)return true;
return false;
}
}
First of all, your Service class creates a new instance of RestTemplate on every request. I cannot stress enough how bad practice it is. Create a bean of type RestTemplate and inject it into your Service bean (it is most likely already created - depending on the Spring MVC version you are using).
Once you have it, then both RestTemplates: one in your Service bean and one injected into FeatureFlipperManagerTest will be the same and testing with MockRestServiceServer will be possible.
EDIT - to be more explicit:
Modify your Service class to:
#Component
public class Service {
private RestTemplate restTemplate;
#Autowired
public Service(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public boolean isEnabled(String xxx) {
ResponseEntity<String> response = restTemplate.getForEntity("someurl",String.class);
if(...)return true;
return false;
}
}
and your test class to:
#RunWith(MockitoJUnitRunner.class)
public class FeatureFlipperManagerTest {
#Autowired
RestTemplate restTemplate;
#Autowired
Service service;
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
#Test
public void test() throws Exception {
mockServer.expect(requestTo(Mockito.anyString()))
.andRespond(withSuccess("{\"enabled\":true}", MediaType.APPLICATION_JSON));
boolean res = service.isEnabled("xxx");
mockServer.verify();
Assert.assertEquals(true, res);
}
}
If this fails with exception saying that there is no RestTemplate bean present then please paste info about version of Spring (Spring Boot?) you are using.
I think you mean you want to use RestTemplate which is provided by spring, so you should createServer after the spring autowired the RestTemplate. I think you can do it like this:
#RunWith(MockitoJUnitRunner.class)
public class FeatureFlipperManagerTest {
#Autowired
RestTemplate restTemplate;
Service service;
MockRestServiceServer mockServer;
#Before
public void init() {
service = new Service();
service.setRestTemplate(restTemplate);
// If you have autowired restTemplate in Service, you can just autowired the service
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void test() throws Exception {
mockServer.expect(requestTo(Mockito.anyString()))
.andRespond(withSuccess("{\"enabled\":true}", MediaType.APPLICATION_JSON));
boolean res = service.isEnabled("xxx");
mockServer.verify();
Assert.assertEquals(true, res);
}
}
This is not an answer to your question, but just in case anyone comes across this question in 2021… With Spring Boot Testing, you may want to take advantage of testing the REST slice only with #RestClientTest. This creates a RestTemplateBuilder bean only by default, if you want an auto-wired RestTemplate just add one bit of configuration as below. (The example is in Kotlin, using Java instead remains as an excercise to the reader.)
#AutoConfigureWebClient(registerRestTemplate = true)
#RestClientTest(Service::class)
class AdkClientTest #Autowired constructor(
private val mockRestServiceServer: MockRestServiceServer,
private val service: Service
) {
// …
}
I'm trying to unit test a Spring 4.0.0 MVC application.
My controller is defined as follow:
#Controller
#RequestMapping("/test")
public class TestCtrl {
#Autowired
private TestService testService;
#Autowired
private TestRessourceAssembler testRessourceAssembler;
#Autowired
private ResponseComposer responseComposer;
#RequestMapping(value = "", method = RequestMethod.GET,produces = "application/json")
public HttpEntity showAll(Pageable pageable) {
Page<Test> patr = testService.getAll(pageable);
return responseComposer.composePage(patr,testRessourceAssembler);
}
#RequestMapping(value = "/{name}", method = RequestMethod.GET)
public HttpEntity<TestRessource> show(#PathVariable String name) {
Test test = testService.getOne(name);
if(test == null){
return new ResponseEntity("Erreur !",HttpStatus.NOT_FOUND);
}
return responseComposer.compose(test,testRessourceAssembler);
}
}
My controller unit test is as follow:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#WebAppConfiguration
#ContextConfiguration(classes = {ApplicationConfig.class, TestMongoConfig.class, RestConfig.class, WebMvcConfig.class})
public class TestCtrlTests{
#InjectMocks
TestCtrl testCtrl;
#Mock
TestService testService;
#Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
when(testService.getOne("jexiste")).thenReturn(new com.thalesgroup.ito.c2s.mc.portail.test.domain.Test("jexiste",1990));
when(testService.getOne("plaf")).thenReturn(null);
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void simpleGetAnswer() throws Exception{
assertNotNull(mockMvc);
mockMvc.perform(get("/test")).andExpect(status().isOk());
mockMvc.perform(get("/test/jexiste")).andExpect(status().isOk());
mockMvc.perform(get("/test/plaf")).andExpect(status().isNotFound());
}
}
When I'm running the test, the "normal" TestService bean is injected and used (I can see the trace in the log), not the mock.
So I read some things on the internet and replaced
this.mockMvc = webAppContextSetup(this.wac).build();
with
this.mockMvc = standaloneSetup(TestCtrl.class).build();
But, and I knew it would happen, I've no more Spring context when doing this, so my PageableArgumentResolver and my other beans (testRessourceAssembler, responseComposer) aren't injected anymore... So they are Null and happen a NullPointerException.
My question is:
1) I'm I designing something wrong ?
2) If not, how can I inject a mock in my controller while keeping other beans from the context ?
Thanks to you !
I'm looked into your tests and this should work. Simply build your MockMvc on your controller with mocked beans. After this all mocks will be visible inside test.
A MockMvcBuilder that accepts #Controller registrations thus allowing full control over the instantiation and the initialization of controllers and their dependencies similar to plain unit tests, and also making it possible to test one controller at a time.
Don't use Spring Integration test! This is simple unit testing!
Fixed test
#RunWith(MockitoJUnitRunner.class)
public class TestCtrlTests{
#InjectMocks
TestCtrl testCtrl;
#Mock
TestService testService;
protected MockMvc mockMvc;
#Before
public void setup(){
when(testService.getOne("jexiste")).thenReturn(new com.thalesgroup.ito.c2s.mc.portail.test.domain.Test("jexiste",1990));
when(testService.getOne("plaf")).thenReturn(null);
this.mockMvc = standaloneSetup(testCtrl).build();
}
#Test
public void simpleGetAnswer() throws Exception{
assertNotNull(mockMvc);
mockMvc.perform(get("/test")).andExpect(status().isOk());
mockMvc.perform(get("/test/jexiste")).andExpect(status().isOk());
mockMvc.perform(get("/test/plaf")).andExpect(status().isNotFound());
}
}