How to validate json schema in spring boot test - spring

I am writing an e2e test for my application.
I have written the following test class
#AutoConfigureMockMvc
#SpringBootTest
public class LineItemControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Test
void testGetLineItems() throws Exception {
mockMvc.perform(get("/service/students")
.queryParam("isArchived", "false")
.queryParam("limit", "10")
.queryParam("offset", "0"))
.andExpectAll(status().isOk(), content().contentType(MediaType.APPLICATION_JSON));
}
}
I would like to validate the response against a JSON schema because the response of this API would be dynamic. So, we can not validate it with any other JSON object.
For example, support the reponse returned by /service/students is
[
{ "Name":"Rahul","age":10}
]
Then my validation would be
response should be an array
each array item will contain a string property named "name"
each array item will container a integer property named "age"
How can i validate it using MocMvc?
Thanks in advance.

Related

Assert that JSON response string matches a certain class with Spring WebMvcTest

Given a simple REST controller in Spring Boot...
#GetMapping("/user/{email}")
public UserResponse getUser(#PathVariable("email") String email) {
return userService.getUserByEmail(email);
}
... I created a #WebMvcTest using MockMvc to assert the JSON content of the UserResponse object. Is it possible to also assert that the JSON string matches the UserResponse class itself (similar to Java's instance of)? I found the following but this does not work:
.andExpect(jsonPath("$", typeCompatibleWith(UserREsponse.class)))

Spring MVC Unit Testing - Mocked Service method is not called

I was working on a spring boot project where I have controller which calls a service method and process the output.
I'am using spring MockMvc for testing the web layer. In my test class I have mocked the service method with Mockito.when(). But when I call the corresponding handler method it is not calling the mocked service method instead returns a null response.
Controller
#Controller
public class SocialLoginEndpoints {
#Autowired
#Qualifier("facebookAuth")
SocialLogin faceBookAuth;
#Autowired
#Qualifier("googleAuth")
SocialLogin googleAuth;
#Autowired SignupService signupService;
#GetMapping("/auth/google")
public String googleAuth(#RequestParam String signupType, HttpServletRequest request) {
return "redirect:" + googleAuth.getAuthURL(request, signupType);
}
}
Test Class
#WebMvcTest(SocialLoginEndpoints.class)
class SocialLoginEndpointsTest {
#Autowired MockMvc mockMvc;
MockHttpServletRequest mockHttpServletRequest;
#MockBean GoogleAuth googleAuth;
#MockBean FacebookAuth facebokAuth;
#MockBean SignupService signupService;
#BeforeEach
void setUp() {
mockHttpServletRequest = new MockHttpServletRequest();
}
#Test
void googleAuth() throws Exception {
Mockito.when(googleAuth.getAuthURL(mockHttpServletRequest, "free"))
.thenReturn("www.google.com");
mockMvc
.perform(MockMvcRequestBuilders.get("/auth/google").param("signupType", "free"))
.andExpect(MockMvcResultMatchers.redirectedUrl("www.google.com"))
.andExpect(MockMvcResultMatchers.status().isFound())
.andDo(MockMvcResultHandlers.print());
Mockito.verify(googleAuth, Mockito.times(1)).getAuthURL(mockHttpServletRequest, "free");
}
The reponse which is returned is
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [Content-Language:"en", Location:"null"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
please help me to resolve this issue.
Thanks in Advance !
You stubbed googleAuth.getAuthURL incorrectly.
The MockHttpServletRequest you create in your test and use during stubbing is not the same instance as HttpServletRequest that is sent via MockMvc. Also, they are not equal to each other (Object.equals is used as it is not overriden)
By default Mockito uses equals to verify if arguments in a stubbing match those in the real call. Your stubbing params do not match call params, thus a default value for the method (null) is returned.
The simplest way to fix is to relax argument matchers.
Mockito.when(googleAuth.getAuthURL(
any(HttpServletRequest.class),
eq("free")))
.thenReturn("www.google.com");

How to mock beans Autowired on service layer in #WebMvcTest

I am testing a REST API's in Spring boot gradle app, my mocked service using #MockBean is returning null. This mocked service return null if there are some beans Autowired in service class(I used constructor injection).
Here is sample Code(Not compiled, only for understanding)
#RestController
#RequestMapping("/xxx")
class TestController {
private RetriveDataService retriveDataService;
public TestControllerx(RetriveDataService retriveDataService) {
this.retriveDataService = retriveDataService;
}
#PostMapping(value = "/yyy")
public MyResponseModel myMethod(#RequestBody MyRequestModel model) {
return retriveDataService.retriveData(model);
}
}
#Service
class RetriveDataService {
private TokenService tokenService;
public RetriveDataService(TokenService tokenService) {
this.tokenService = tokenService;
}
public MyResponseModel retriveData(MyRequestModel model) {
String accessToken = tokenService.getToken().getAccessToken();
return retriveData(model, accessToken);
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(TestController.class)
public class TestControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private RetriveDataService retriveDataService;
#Test
public void testRetriveData() throws Exception {
mvc.perform(MockMvcRequestBuilders.post("/xxx/yyy").content(objectMapper.writeValueAsString(new MyRequestModel()))
.contentType(MediaType.APPLICATION_JSON_UTF8)).andDo(MockMvcResultHandlers.print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
When I run this test, i am getting following output(If my service do not need another bean, I am getting expected output)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Due to this response i facing problem on line .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));. also when i check response body(as body is also a null)
Sample project to reproduce the issue is here
Checking your repository confirmed assumption form the discussion in comments under question.
You specify expectations on your mock
MyModel requestMessage = new MyModel();
requestMessage.setMessage("Hello Request Post");
given(testService1.getMessage(requestMessage)).willReturn(responseMessage);
but the message received to in your controller in your #WebMvcTest is not equal to requestMessage specified in the test. This is due to the fact that MyModel class does not override equals method.
In this situation, Mockito will use its default behaviour:
By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.
You have two options to fix the problem:
override equals (and hashCode) in your request class.
Get acquainted with argument matchers
More info on option 2.:
Technically, your expectation is equivalent to:
given(testService1.getMessage(ArgumentMatchers.eq(requestMessage)))
.willReturn(responseMessage);
You can use other matcher, or even define your own. This is useful if you cannot modify code of your argument's type (type coming from 3-rd party library etc).
For example, you can use ArgumentMatchers.any(MyModel.class))

Spring 5 with JUnit 5 + Mockito - Controller method returns null

I try to test a method named as loadData defined in MainController which returns result as a string. Despite that this method actually returns data when the web app runs on servlet container (or when I debug the code), no data returns when I invoke it from a test class based on JUnit 5 with Mockito.
Here is my configuration:
#ContextConfiguration(classes = {WebAppInitializer.class, AppConfig.class, WebConfig.class})
#Transactional
#WebAppConfiguration
public class TestMainController {
#InjectMocks
private MainController mainController;
private MockMvc mockMvc;
#BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(this.mainController).build();
}
#Test
public void testLoadData() throws Exception {
MvcResult mvcResult = mockMvc
.perform(MockMvcRequestBuilders.get("/loadData.ajax"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
Assertions.assertNotNull(mvcResult.getResponse().getContentAsString(), "response should not be null");
}
}
The test fails due to java.lang.NullPointerException as the this.mainController is null.
Environment Details:
Spring version: 5.0.3
JUnit version: 5.0.3
mockito version: 1.9.5
hamcrest version: 1.3
json-path-assert version: 2.2.0
Edit: Here is the loadData method of MainController:
#RequestMapping(value = "/loadData.ajax", method = RequestMethod.GET)
public String loadData(HttpServletRequest request, HttpServletResponse response) {
List list = mainService.loadData(); // starts a transaction and invokes the loadData method of mainDAO repository which basically loads data from the database
return JSONArray.fromObject(list).toString();
}
You can call controller method directly, just like we do for service method, but this is not recommended. Using MockMvc, you check for header and request param mapping are proper. Plus, you also check for end point mapping is correct. Plus the Request METHOD is also correct. All these you cannot test if you test your code by directly calling the controller method.
One thing you can try is, instead of creating new object inside the standalone context, use the Mock. i.e
mockMvc = MockMvcBuilders.standaloneSetup(this. mainController).build();
And while calling, do this
MvcResult mvcResult = mockMvc
.perform(MockMvcRequestBuilders.get("/loadData.ajax"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
Assert , what you would like to
Assert.assertEquals("response does not match", mvcResult.getResponse().getContentAsString(),
"some expected response");
You are getting null or 400 or 404 http status ?
If you are getting 400, then please check the header and req. param if any are proper. If you are getting 404 then please check the URL path. /loadData.ajax , assuming this is your request mapping path in controller method.

Using Spring's MockMvc framework, how do I test the value of an attribute of an attribute of my model?

I’m using Spring 3.2.11.RELEASE and JUnit 4.11. I’m using Spring’s org.springframework.test.web.servlet.MockMvc framework to test a controller method. In one test, I have a model that is populated with the following object:
public class MyObjectForm
{
private List<MyObject> myobjects;
public List<MyObject> getMyObjects() {
return myobjects;
}
public void setMyObjects(List<MyObject> myobjects) {
this.myobjects = myobjects;
}
}
The “MyObject” object in turn has the following field …
public class MyObject
{
…
private Boolean myProperty;
Using the MockMvc framework, how do I check that the first item in the “myobjects” list has an attribute “myProperty” equal to true? So far I know it goes something like this …
mockMvc.perform(get(“/my-path/get-page”)
.param(“param1”, ids))
.andExpect(status().isOk())
.andExpect(model().attribute("MyObjectForm", hasProperty("myobjects[0].myProperty”, Matchers.equalTo(true))))
.andExpect(view().name("assessment/upload"));
but I’m clueless as to how to test the value of an attribute of an attribute?
You can nest hasItem and hasProperty matchers if your object has a getter getMyProperty.
.andExpect(model().attribute("MyObjectForm",
hasProperty("myObjects",
hasItem(hasProperty("myProperty”, Matchers.equalTo(true))))))
If you know how much objects are in the list than you can check the first item with
.andExpect(model().attribute("MyObjectForm",
hasProperty("myObjects", contains(
hasProperty("myProperty”, Matchers.equalTo(true)),
any(MyObject.class),
...
any(MyObject.class)))));
In case anyone else comes across this problem. I ran into a similar issue trying to test a value of an attribute (firstName), of a class (Customer) in a List< Customer >. Here is what worked for me:
.andExpect(model().attribute("customerList", Matchers.hasItemInArray(Matchers.<Customer> hasProperty("firstName", Matchers.equalToIgnoringCase("Jean-Luc")))))

Resources