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() );
}
}
Related
I need to write a unit test for a rest controller endpoint that calls a service which calls RestTemplate exchange()
#RestController
public class MyController {
#Autowired
Myservice myservice;
#GetMapping("todo")
public ResponseEntity<String> getTodo() {
String todoJson = myservice.getTodo();
return ResponseEntity.ok(todoJson);
}
}
Here's my service class
#Service
public class Myservice {
public String getTodo() {
ResponseEntity<Todo> response = restTemplate.exchange("https://jsonplaceholder.typicode.com/todos/1", HttpMethod.GET, null, Todo.class);
Todo todo = response.getBody();
return objectMapper.writeValueAsString(todo);
}
}
And test case
#ExtendWith(SpringExtension.class)
class MyControllerTestJunit5 {
private MockMvc mockMvc;
#Mock
private RestTemplate restTemplate;
#InjectMocks
MyController myController;
#Mock
Myservice myservice;
#BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
}
#Test
public void mockResttemplate() throws Exception {
Todo todosample = new Todo(5,6,"myfield", true);
ResponseEntity<Todo> responseEntity = ResponseEntity.ok(todosample);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.eq(Todo.class)))
.thenReturn(responseEntity);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/todo"))
.andExpect(status().isOk())
.andReturn();
String myresult = result.getResponse().getContentAsString();
System.out.println("response: " + myresult);
}
printing the response at the end shows that the response is empty. How can I get this to work?
I assume that you expect to get the JSON representation of todosample object. The problem in the test is that you are mocking Myservice by annotating it with #Mock. That mocked service is used by MyController because it's annotated with #InjectMocks. As a result, when you make a call to the /todo endpoint the controller calls myservice.getTodo() which returns null as the Myservice mock is not configured to return anything else. After that, the null value gets propagated to the ResponseEntity body which materializes to the empty OK response in your test.
I believe a better approach for this kind of test will be to just mock Myservice and configure it properly. The first part is done already as I mentioned. The configuration is easy. You should just replace restTemplate.exchange call configuration with the myservice.getTodo() one. Something like this should work:
#Test
public void mockResttemplate() throws Exception {
String todosampleJson = "{}"; // just empty json
when(myservice.getTodo()).thenReturn(todosampleJson);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/todo"))
.andExpect(status().isOk())
.andReturn();
String myresult = result.getResponse().getContentAsString();
System.out.println("response: " + myresult); // will print "{}" now
}
And then you can have a separate test for Myservice where you have to mock restTemplate.exchange call.
Of course technically, you can still get away with mocking the restTemplate in your MyController test but then you have to make sure you instantiate the real Myservice instead of the mocked version of it. However, in this case you would expose the implementation details of Myservice to the MyController class, which kinda contradicts the MVC pattern.
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.
I'm consuming a remote api in my Spring MVC project using RestTemplate. One client is for authentication named LoginApiClient and I have to use this in all other rest clients.
This one my rest client that uses LoginApiClient and consumes QrCode Api.
#Service
public class QrKeyApiClient implements QrKeyApiClientBase {
private RestTemplate restTemplate;
#Autowired
private LoginApiClientBase loginApiClient;
public QrKeyApiClient(RestTemplateBuilder builder) {
restTemplate = builder.build();
restTemplate.setErrorHandler(new ErrorHandler());
}
//Other implementation details
}
And this is LoginApiClient
#Service
public class LoginApiClient implements LoginApiClientBase {
private RestTemplate restTemplate;
public LoginApiClient(RestTemplateBuilder builder) {
restTemplate = builder.build();
restTemplate.setErrorHandler(new ErrorHandler());
}
//Other implementation details
}
My unit test class for QrKeyApiClient is like below.
#RunWith(SpringRunner.class)
#RestClientTest({QrKeyApiClient.class})
#Category(IUnitTest.class)
public class QrKeyApiClientTest {
#Value("${getQrCodeUrl}")
private String getQrCodeUrl;
#Mock
LoginApiClient loginApiClient;
#Autowired
private MockRestServiceServer server;
#InjectMocks
private QrKeyApiClient client;
#Test
public void getQrCodeAsImage_makesTrueCallToApi() throws Exception {
ResponseEntity<String> responseEntity = mock(ResponseEntity.class);
HttpEntity requestEntity = new HttpEntity<>(qrCodeGenerateRequest,new HttpHeaders());
this.server
.expect(requestTo(this.getQrCodeUrl))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("successResult", MediaType.TEXT_PLAIN));
String qrImage = this.client.getQrCodeAsImage(qrCodeGenerateRequest);
server.verify();
assertThat(qrImage, is(notNullValue(String.class)));
}
}
I'm not sure this is the right way to do this but I want to mock my LoginApiClient in this test and inject it to QrKeyApiClient. But my test not passes with an error that says "Error creating bean with name 'qrKeyApiClient': Unsatisfied dependency expressed through field 'loginApiClient';"
So how I can test this client with mock of other clients that member of it.
It seems the problem is a mixed usage of Spring and Mockito annotations. Spring provides the #MockBean annotation to mock services in the application context.
Try replacing #Mock with #MockBean and #InjectMocks with #Autowired. The requestEntity and responseEntity are not needed then probably.
#RunWith(SpringRunner.class)
#RestClientTest({QrKeyApiClient.class})
#Category(IUnitTest.class)
public class QrKeyApiClientTest {
#Value("${getQrCodeUrl}")
private String getQrCodeUrl;
#MockBean
LoginApiClient loginApiClient;
#Autowired
private MockRestServiceServer server;
#Autowired
private QrKeyApiClient client;
#Test
public void getQrCodeAsImage_makesTrueCallToApi() throws Exception {
this.server
.expect(requestTo(this.getQrCodeUrl))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("successResult", MediaType.TEXT_PLAIN));
String qrImage = this.client.getQrCodeAsImage(qrCodeGenerateRequest);
server.verify();
assertThat(qrImage, is(notNullValue(String.class)));
}
}
#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());
}
}