I'm working to test (via JUnit4 and Spring MockMvc) a REST service adapter using Spring-boot. The adapter simply passes along requests made to it, to another REST service (using a custom RestTemplate) and appends additional data to the responses.
I'd like to run MockMvc tests to perform controller integration tests, but want to override the RestTemplate in the controller with a mock to allow me to predefine the 3rd party REST response and prevent it from being hit during each test. I've been able to accomplish this by instantiating a MockMvcBuilders.standAloneSetup() and passing it the controller to be tested with the mock injected as listed in this post (and my setup below), however I am not able to do the same using MockMvcBuilders.webAppContextSetup().
I've been through a few other posts, none of which answer the question as to how this might be accomplished. I would like to use the actual Spring application context for the tests instead of a standalone to prevent any gaps as the application is likely to grow.
EDIT: I am using Mockito as my mocking framework and am trying to inject one of its mocks into the context. If this isn't necessary, all the better.
Controller:
#RestController
#RequestMapping(Constants.REQUEST_MAPPING_PATH)
public class Controller{
#Autowired
private DataProvider dp;
#Autowired
private RestTemplate template;
#RequestMapping(value = Constants.REQUEST_MAPPING_RESOURCE, method = RequestMethod.GET)
public Response getResponse(
#RequestParam(required = true) String data,
#RequestParam(required = false, defaultValue = "80") String minScore
) throws Exception {
Response resp = new Response();
// Set the request params from the client request
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(Constants.PARAM_DATA, data);
parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);
resp = template.getForObject(Constants.RESTDATAPROVIDER_URL, Response.class, parameters);
if(resp.getError() == null){
resp.filterScoreLessThan(new BigDecimal(minScore));
new DataHandler(dp).populateData(resp.getData());
}
return resp;
}
}
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
#TestPropertySource("/application-junit.properties")
public class WacControllerTest {
private static String controllerURL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
private static String compressedParams_all = "?data={data}&minScore={minScore}";
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#InjectMocks
private Controller Controller;
#Mock
private RestTemplate rt;
#Value("${file}")
private String file;
#Spy
private DataProvider dp;
#Before
public void setup() throws Exception {
dp = new DataProvider(file);
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testGetResponse() throws Exception {
String[] strings = {"requestData", "100"};
Mockito.when(
rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
.thenReturn(populateTestResponse());
mockMvc.perform(get(controllerURL, strings)
.accept(Constants.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultHandlers.print());
Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());
}
private Response populateTestResponse() {
Response resp = new Response();
resp.setScore(new BigDecimal(100));
resp.setData("Some Data");
return resp;
}
}
Spring's MockRestServiceServer is exactly what you're looking for.
Short description from javadoc of the class:
Main entry point for client-side REST testing. Used for tests that involve direct or indirect (through client code) use of the RestTemplate. Provides a way to set up fine-grained expectations on the requests that will be performed through the RestTemplate and a way to define the responses to send back removing the need for an actual running server.
Try to set up your test like this:
#WebAppConfiguration
#ContextConfiguration(classes = {YourSpringConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class ExampleResourceTest {
private MockMvc mockMvc;
private MockRestServiceServer mockRestServiceServer;
#Autowired
private WebApplicationContext wac;
#Autowired
private RestOperations restOperations;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
mockRestServiceServer = MockRestServiceServer.createServer((RestTemplate) restOperations);
}
#Test
public void testMyApiCall() throws Exception {
// Following line verifies that our code behind /api/my/endpoint made a REST PUT
// with expected parameters to remote service successfully
expectRestCallSuccess();
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/my/endpoint"))
.andExpect(status().isOk());
}
private void expectRestCallSuccess() {
mockRestServiceServer.expect(
requestTo("http://remote.rest.service/api/resource"))
.andExpect(method(PUT))
.andRespond(withSuccess("{\"message\": \"hello\"}", APPLICATION_JSON));
}
}
Here's another solution. Simply put, it just creates a new RestTemplate bean and overrides the one already registered.
So while it performs produces the same functionality as #mzc answer, it allows me to use Mockito to craft the response and verification matchers a bit easier.
Not that it's more than a couple lines of code, but it also prevents from having to add additional code to convert from the Response object to a string for the above mockRestServiceServer.expect().andRespond(<String>) method's arg.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
#TestPropertySource("/application-junit.properties")
public class WacControllerTest {
private static String Controller_URL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
#Configuration
static class Config {
#Bean
#Primary
public RestTemplate restTemplateMock() {
return Mockito.mock(RestTemplate.class);
}
}
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#InjectMocks
private Controller Controller;
#Mock
private RestTemplate rt;
#Value("${file}")
private String file;
#Spy
private DataProvider dp;
#Before
public void setup() throws Exception {
dp = new DataProvider(file);
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.rt = (RestTemplate) this.wac.getBean("restTemplateMock");
}
#Test
public void testGetResponse() throws Exception {
String[] strings = {"request", "100"};
//Set the request params from the client request
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(Constants.PARAM_SINGLELINE, strings[0]);
parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);
Mockito.when(
rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
.thenReturn(populateTestResponse());
mockMvc.perform(get(Controller_URL, strings)
.accept(Constants.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultHandlers.print());
Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());
}
private Response populateTestResponse() {
Response resp = new Response();
resp.setScore(new BigDecimal(100));
resp.setData("Some Data");
return resp;
}
}
org.springframework.boot.test.mock.mockito.MockBean #MockBean helped me out.
Related
I've got a REST controller I'm trying to test, but when attempting to POST to it, I get a 404. The test is JUnit 5 and Spring Boot 2.1.5. If I run the application, I can hit the controller via Postman. I've run it in debug mode and verified that myController is not null and has the mocked services injected into it. What am I missing here? spring-boot-starter-test is a dependency, and junit4 is an exclusion.
#RestController
#Slf4j
#RequestMapping(path = /integrations,
produces = "application/json")
public class MyController {
private MyService myService;
private MyValidationService myValidationService;
public MyController(MySerivce service, MyValidationService myValidationService) {
this.myService = service;
this.myValidationService = myValidationService;
}
#PostMapping(path = "/users", produces = "application/json", consumes =
"application/json")
public ResponseEntity<User> getUserList(#Valid #RequestBody final RequestPayload
requestPayload, #RequestHeader Map<String, String> headers) throws MyException {
// check the credentials and permission
Map<String, String> credInfo = myValidationService.validateHeaders(headers);
// process payload
return myService.retrieveUsers(requestPayload);
}
}
Test is as follows:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
class MyControllerTest {
#MockBean
private MyService myService;
#MockBean
private MyValidationService myValidationService;
#Autowired
private MockMvc mockMvc;
#Autowired
MyController myController;
#Test
public void contextLoads() throws Exception {
Assert.assertNotNull(myController);
}
#Test
void getUserList() throws Exception {
List<User> users = returnUserList();
HttpEntity<RequestPayload> requestHttpEntity = new HttpEntity<>(returnPayload(), null);
when(myService.retrieveUsers(any(RequestPayload.class))).thenReturn(users);
mockMvc.perform(post("/integrations/users")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(returnPayload()))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().is2xxSuccessful());
}
}
The response I get is:
MockHttpServletResponse:
Status = 404
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I appreciate the answers, but I discovered what the problem was - and it was a bonehead one!
It turns out that I included the context path in the mockMvc call when that value is already provided in the application.properties file! So instead of a URI of /integrations/users, the real URI being used was /integrations/integrations/users, which unsurprisingly does not exist!
Thanks to all and sorry for not taking my eyes in my hands and looking more closely.
You can use a slice test annotation instead. For testing a controller you can use #WebMvcTest.
Your setup would look like this:
#SpringBootTest(value = MyController.class)
#ExtendWith(SpringExtension.class)
class MyControllerTest {
You can still #Autowired the MockMvc. Also there is no need to autowire your controller in the test.
I have a SpringBoot application with this method in the controller to create an user in the database. The controller is working fine in Postman.
#RestController
#RequestMapping("/v1")
public class UserController {
#PostMapping(value = "/user/{id}")
public void createUser(#PathVariable Integer id, #Valid #RequestBody User request,
BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
throw new RequestValidationException(VALIDATION_ERRORS, bindingResult.getFieldErrors());
}
userService.createUser(id, request), HttpStatus.CREATED);
}
Now I have a junit test case to test this method and I am getting a 404
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
public class UserTest {
private MockMvc mockMvc;
final String CREATE_USER_URL = "/v1/user/" + "10";
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void testCreateUser() throws Exception {
mockMvc.perform(post(CREATE_USER_URL)
// doesn't work either if I put "/v1/user/10" or post("/v1/user/{id}", 10) here
.content(TestUtils.toJson(request, false))
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andReturn();
}
But in the log, I was able to see the correct url:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v1/user/10
Parameters = {}
Can someone please let me know why I am getting a 404 NOT Found? Thanks.
From docs you need #AutoConfigureMockMvc on class and #Autowire MockMvc
Another useful approach is to not start the server at all, but test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost the full stack is used, and your code will be called exactly the same way as if it was processing a real HTTP request, but without the cost of starting the server. To do that we will use Spring’s MockMvc, and we can ask for that to be injected for us by using the #AutoConfigureMockMvc annotation on the test case:
Code :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class UserTest {
#Autowire
private MockMvc mockMvc;
final String CREATE_USER_URL = "/v1/user/" + "10";
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void testCreateUser() throws Exception {
mockMvc.perform(post(CREATE_USER_URL)
// doesn't work either if I put "/v1/user/10" or post("/v1/user/{id}", 10) here
.content(TestUtils.toJson(request, false))
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andReturn();
}
}
If want to Test your real springboot url Test (End to end Test)
u can use rest-assured or resttemplte
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class)
#TestPropertySource(value={"classpath:application.properties"})
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class SpringRestControllerTest {
#Value("${server.port}")
int port;
#Test
public void getDataTest() {
get("/api/tdd/responseData").then().assertThat().body("data", equalTo("responseData"));
}
#Before
public void setBaseUri () {
RestAssured.port = port;
RestAssured.baseURI = "http://localhost"; // replace as appropriate
}
}
https://dzone.com/articles/test-driven-development-with-spring-boot-rest-api
I try to test a #RestController within a integration test suite using MockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class WebControllerIT {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The #RestController (WebController) calls an injected #Service (RestClientService) which uses RestTemplate to call another REST server. This leads to the following error when running the test.
org.springframework.web.client.ResourceAccessException: I/O error on
GET request for "http://test123.com/42/status": test123.com; nested
exception is java.net.UnknownHostException: test123.com
I used MockRestServiceServer for the integration test of the #Service itself but have no idea how to archieve this within the test of #RestController.
How can I simulate a correct REST call of the RestTemplate?
The #RestController class.
#RestController
public class WebController {
private final RestClientService service;
#Autowired
public WebController(RestClientService service) {this.service = service;}
#GetMapping("/status")
public String getStatus() {
// extract pid from database ...
int pid = 42;
return this.service.getStatus(42);
}
}
The #Serviceclass.
#Service
public class RestClientService {
private final RestTemplate restTemplate;
public RestClientService(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
}
Integration/Unit testing doesn't work that way.Objective of this kind of testing is to run through your code and make sure all the business requirement are met but not to hit other system or DB.Here in your case u shouldn't be hitting test123.com to get back data.What needs to done here is that you should mock that method.
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
So that control doesn't enter this method but return you back the mock data(Dummy data).
For example let say that there are 2 kind of status this method is returning and you need to do some business validation based on the string returned.In this case u need to write 2 integration test and make sure the mocking method returns 2 different value(Dummy value instead of hitting that end point)
Reason why we are writing unit testing/integration testing is to make sure your entire code is working as expected but not to hit other system from ur code.
If you want to only test your controller layer, you would do like this.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MockServletContext.class)
#WebAppConfiguration
public class WebControllerIT {
private MockMvc mockMvc;
private RestClientService service
#Mock
private RestTemplate restTemplate
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
service = new RestClientService(restTemplate);
WebController webController = new WebController(service);
mvc = MockMvcBuilders.standaloneSetup(webController).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
//Mock the behaviour of restTemplate.
doReturn("someString").when(restTemplate).getForObject(anyString(), anyString());
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
I have a spring boot application which contains a RestEasy webservice created using #Service like:
#Path("/developers")
#Service
public interface DeveloperResource {
#POST
#Produces("application/json")
#Consumes("application/json")
Response create(#RequestBody List<DeveloperDto> developers);
}
and I have the the according integration test class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
public class DeveloperResourceTest {
public static final String URI = "http://localhost:8080/developers";
public static final DeveloperDto DEVELOPER = new DeveloperDto(null, "toto");
public static final List<DeveloperDto> DEVELOPERS_COLLECTION = Collections.singletonList(DEVELOPER);
public static final DeveloperEntity DEVELOPER_MAPPED_TO_ENTITY = DeveloperMapper.toEntity(DEVELOPER);
public static final String DEVELOPER_COLLECTION_IN_JSON = "[{\"developerId\":null,\"developerName\":\"toto\",\"programmingLanguages\":null}]";
private MockMvc mockMvc;
#MockBean
DeveloperService service;
#Mock
private DeveloperResource tested;
#Autowired
WebApplicationContext webApplicationContext;
private ObjectMapper mapperJson;
#Before
public void setUp() throws Exception {
tested=new DeveloperResourceImpl(service);
mockMvc=MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
mapperJson = new ObjectMapper();
}
#Test
public void should_create_a_developer_and_return_OK() throws Exception {
Mockito.when(service.save(DEVELOPER_MAPPED_TO_ENTITY)).thenReturn(Optional.of(DEVELOPER_MAPPED_TO_ENTITY));
tested.create(DEVELOPERS_COLLECTION);
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post(URI)
.content(DEVELOPER_COLLECTION_IN_JSON)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
assertEquals(HttpStatus.CREATED.value(), response.getStatus());
assertEquals(URI, response.getHeader(HttpHeaders.LOCATION));
}
}
After the execution of the test I got:
java.lang.AssertionError:
Expected :201
Actual :404
My questions are:
Is my integration test configuration apropriate?
Can we use MockMvc even if the created REST web service isn't created using #RestController or #Controller?.
Thank you in advance
#Service will not be recognized as a rest service. You should annotate it as #RestController on a class, actually implementing the create method.
see: #Service
You can’t use MockMvc to test an app that doesn’t use Spring MVC. RESTdocs has support for Rest Assured which works over HTTP so it doesn’t depend on the web stack.
Repository object not mocked from controller testcase return empty object here is the below code
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Main.class)
#WebAppConfiguration
#ActiveProfiles(ApplicationConstants.DEVELOPMENT_PROFILE)
public class EmployeeControllerRealTest {
#Autowired
private WebApplicationContext webAppContext;
private MockMvc mockMvc;
#Mock
EmployeeCompositeService employeeCompositeService;
String name = "mike";
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();
MockitoAnnotations.initMocks(this);
}
#Test
public void testGetEmployees() throws Exception {
Mockito.when(employeeRepository.findByName(name)).thenReturn(getEmployees());
String url = URIConstants.ROOT_CONTEXT + URIConstants.EMPLOYEE;
MvcResult result =
mockMvc.perform(post(url)
.contentType(APPLICATION_JSON_UTF8)
.content(convertObjectToJsonBytes(name))
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$[0].employeeName").value("Mike"))
.andReturn();
String jsonContent = result.getResponse().getContentAsString();
LOGGER.debug("jsonContent: {}",jsonContent);
}
protected byte[] convertObjectToJsonBytes(Object object) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.writeValueAsBytes(object);
}
private List<Employee> getEmployees(){
//here is the logic to get List of employees to return. When the mockito call is invoked.
}
}
I have a service call in side EmployeeController i.e employeeCompositeService.getEmployees(String name)
So I have mocked in the EmployeeControllerTestcase i.e #Mock EmployeeCompositeService employeeCompositeService;
when I run the controller testcase it invokes further services calls and repository and hit the database
So I does want to call those services returns the results form my employeeCompositeService.getEmployees(String name) from controller.
Can you please tell me what I did wrong in the above code Thanks in Advance