SpringBoot Unit test - Service method is not invoked - java

I am new to Unit Testing. After referring google, I created a Test class to test my Controller as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(PromoController.class)
public class PromoApplicationTests {
#Autowired
protected MockMvc mvc;
#MockBean PromoService promoService;
protected String mapToJson(Object obj) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, clazz);
}
#Test
public void applyPromotionTest_1() throws Exception {
String uri = "/classPath/methodPath";
List<Cart> cartLs = new ArrayList<Cart>();
// added few objects to the list
String inputJson = mapToJson(cartLs);
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(uri)
.contentType(MediaType.APPLICATION_JSON).content(inputJson)).andReturn();
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
String actual = mvcResult.getResponse().getContentAsString();
String expected = "{\"key1\":val1, \"key2\":\"val 2\"}";
assertEquals(expected, actual, true);
}
}
I have the following Controller and service Class :
#RequestMapping("/classPath")
#RestController
public class PromoController {
#Autowired
PromoService promoService;
#PostMapping("/methodPath")
public PromoResponse applyPromo(#RequestBody List<Cart> cartObj) {
PromoResponse p = promoService.myMethod(cartObj);
return p;
}
}
#Component
public class PromoServiceImpl implements PromoService{
#Override
public PromoResponse myMethod(List<Cart> cartList) {
// myCode
}
}
When I debugged my unit test, p object in the controller was null.
I am getting status as 200 but not the expected JSON response
What am I missing here?

While using #WebMvcTest spring boot will only initialize the web layer and will not load complete application context. And you need to use #MockBean to create and inject mock while using #WebMvcTest.
Which you have done
#MockBean
PromoService promoService;
Spring Boot instantiates only the web layer rather than the whole context
We use #MockBean to create and inject a mock for the GreetingService (if you do not do so, the application context cannot start), and we set its expectations using Mockito.
But the, since it is mock bean you are responsible to mock the myMethod() call
#Test
public void applyPromotionTest_1() throws Exception {
String uri = "/classPath/methodPath";
List<Cart> cartLs = new ArrayList<Cart>();
// added few objects to the list
// create PromoResponse object you like to return
When(promoService.myMethod(ArgumentsMatchesr.anyList())).thenReturn(/*PromoResponse object */);
String inputJson = mapToJson(cartLs);
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(uri)
.contentType(MediaType.APPLICATION_JSON).content(inputJson)).andReturn();
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
String actual = mvcResult.getResponse().getContentAsString();
String expected = "{\"key1\":val1, \"key2\":\"val 2\"}";
assertEquals(expected, actual, true);
}

Related

Spring Boot / Mockito: Mocking RestTemplate but Response Always Null

I'm having problems mocking the response object of my Test Class when using Mockito. I'm trying to test an exception, for this I need one of the attributes of the Class that returns from the POST request. I've successfully mocked the RestTemplate but my when().thenReturn() is not returning anything and I'm getting a null pointer exception at the "if" validation. If anyone could help me on this problem I would be very grateful.
Here is my Service Class:
#Service
public class CaptchaValidatorServiceImpl implements CaptchaValidatorService{
private static final String GOOGLE_CAPTCHA_ENDPOINT = "someEndpoint";
private String stage;
private String captchaSecret;
private RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
#Override
public void checkToken(String token) throws Exception{
MultiValueMap<String,String> requestMap = new LinkedValueMap<>();
requestMap.add("secret", captchaSecret);
requestMap.add("response", token);
try{
CaptchaResponse response = restTemplate.postForObject(GOOGLE_CAPTCHA_ENDPOINT,
requestMap, CaptchaResponse.class);
if(!response.getSuccess()){
throw new InvalidCaptchaTokenException("Invalid Token");
}
} catch (ResourceAccessException e){
throw new CaptchaValidationNotPossible("No Response from Server");
}
}
private SimpleClientHttpRequestFactory getClientHttpRequestFactory(){
...
}
}
And here is my Test Class:
#SpringBootTest
public class CaptchaValidatorTest{
#Mock
private RestTemplate restTemplate;
#InjectMocks
#Spy
private CaptchaValidatorServiceImpl captchaValidatorService;
private CaptchaResponse captchaResponse = mock(CaptchaResponse.class);
#Test
public void shouldThrowInvalidTokenException() {
captchaResponse.setSuccess(false);
Mockito.when(restTemplate.postForObject(Mockito.anyString(),
ArgumentMatchers.any(Class.class), ArgumentMatchers.any(Class.class)))
.thenReturn(captchaResponse);
Exception exception = assertThrows(InvalidCaptchaTokenException.class, () ->
captchaValidatorService.checkToken("test"));
assertEquals("Invalid Token", exception.getMessage());
}
}
In my opinion it could be a problem with ArgumentMatchers.
Method postForObject require parameters as String, MultiValueMap(or parent) and Class, but you set in Mockito.when: anyString() (correct), any(Class.class) (but MultiValueMap is passed - probably incorrect) and any(Class.class) (correct).
Try use:
Mockito.when(restTemplate.postForObject(ArgumentMatchers.any(String.class),
ArgumentMatchers.any(MultiValueMap.class), ArgumentMatchers.any(Class.class)))
.thenReturn(captchaResponse);
EDIT:
It seems to me that the CaptchaResponse in the test is unnecessarily a mock:
private CaptchaResponse captchaResponse = mock(CaptchaResponse.class);
but if You want this in that way, I think u need to replace:
captchaResponse.setSuccess(false);
to something like:
Mockito.when(captchaResponse.getSuccess()).thenReturn(false);

Mocking of Resttemplate failed in spring boot junit

I am writing junit test cases for the method which is call an rest api,following is the code I have tried:
#RunWith(MockitoJUnitRunner.class)
public class NotificationApiClientTests {
#Mock
private RestTemplate restTemplate;
#InjectMocks
private NotificationApiClient notificationApiClient;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(notificationApiClient, "notificationUrl", "myURL***");
}
#Test
public void test_NotificationClickAPI_Call() throws JsonParseException, JsonMappingException, IOException {
ResponseEntity<NotificationClickEvent[]> notificationClickEventList = util.getValidNotificationEvent_ResponseEntity();
Mockito.when(restTemplate.exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<NotificationClickEvent[]>> any()
)
).thenReturn(notificationClickEventList);
NotificationClickEvent[] notificationArray = notificationApiClient.requestNotificationClick(Const.NotificationClick, "2018-07-31-10");
assertTrue(notificationArray.length>0);
}
}
and in My NotificationApiClient , it was:
#Value("${notification.base.url}")
private String notificationUrl;
public NotificationApiClient() {
}
public UserInfoEvent[] requestUserInfo(String eventType, String dateStr) {
HttpEntity request = new HttpEntity(setHttpHeaders());
ResponseEntity<UserInfoEvent[]> response = this.exchange(
notificationUrl + eventType + "&dateStr=" + dateStr,
HttpMethod.GET, request, UserInfoEvent[].class);
UserInfoEvent[] userInfoRequest = response.getBody();
return userInfoRequest;
}
but it's not working, as per my code whenever the resttemplate.exchange method is called it should return the notificationClickEventList, But its calling the real api and returns the api result as the list.
Can anyone please help me to solve it?
In your code you are not using restTemplate.exchange method, It seems you are using notificationApiClient's exchange method. So try this.
#Spy
#InjectMocks
private NotificationApiClient notificationApiClient;
Mockito.when(notificationApiClient.exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<NotificationClickEvent[]>> any()
)
).thenReturn(notificationClickEventList);

How to build get-query string for MockMvcRequestBuilders?

I'm using spring-boot-test with MockMvcRequestBuilders to test some GET rest webservice.
Question: is it possible to automatically translate a bean to a get-query?
Example:
#AutoConfigureMockMvc
public class WebTest {
#Autowired
protected MockMvc mvc;
#Test
public void test() {
MyRequest req = new MyRequest();
req.setFirstname("john");
req.setLastname("doe");
req.setAge(30);
mvc.perform(MockMvcRequestBuilders
.get(path)
.contentType(MediaType.APPLICATION_JSON)
.param(...) //TODO how to automatically add all params?
.andExpect(status().isOk());
}
}
public class MyRequest {
private String firstname;
private String lastname;
private int age;
}
I would need an auto translation to: ?firstname=john&lastname=doe&age=30, but in a more generic way not having to type the parameters statically.
I don't think there's anything available out-of-the-box for this specific requirement, but you can piece it together using a BeanWrapperImpl to access the properties from MyRequest and turn each into a call to param on the request builder:
MyRequest req = new MyRequest();
req.setFirstname("john");
req.setLastname("doe");
req.setAge(30);
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders
.get(path).contentType(MediaType.APPLICATION_JSON);
for (PropertyDescriptor property : new BeanWrapperImpl(req).getPropertyDescriptors()) {
if (property.getWriteMethod() != null) {
requestBuilder.param(property.getName(),
property.getReadMethod().invoke(req).toString());
}
}
mvc.perform(requestBuilder).andExpect(status().isOk());

Spring REST mock context path

I try to set the context path for spring rest mocks using the following code snippet:
private MockMvc mockMvc;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(document("{method-name}/{step}/",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())))
.build();
}
#Test
public void index() throws Exception {
this.mockMvc.perform(get("/").contextPath("/api").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("_links.business-cases", is(notNullValue())));
}
But I receive the following error:
java.lang.IllegalArgumentException: requestURI [/] does not start with contextPath [/api]
What is wrong?
Is it possible to specify the contextPath at a single place in code e.g. directly in the builder?
edit
here the controller
#RestController
#RequestMapping(value = "/business-case", produces = MediaType.APPLICATION_JSON_VALUE)
public class BusinessCaseController {
private static final Logger LOG = LoggerFactory.getLogger(BusinessCaseController.class);
private final BusinessCaseService businessCaseService;
#Autowired
public BusinessCaseController(BusinessCaseService businessCaseService) {
this.businessCaseService = businessCaseService;
}
#Transactional(rollbackFor = Throwable.class, readOnly = true)
#RequestMapping(value = "/{businessCaseId}", method = RequestMethod.GET)
public BusinessCaseDTO getBusinessCase(#PathVariable("businessCaseId") Integer businessCaseId) {
LOG.info("GET business-case for " + businessCaseId);
return businessCaseService.findOne(businessCaseId);
}
}
You need to include the context path in the path that you're passing to get.
In the case you've shown in the question, the context path is /api and you want to make a request to / so you need to pass /api/ to get:
#Test
public void index() throws Exception {
this.mockMvc.perform(get("/api/").contextPath("/api").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("_links.business-cases", is(notNullValue())));
}
What many people do is simply not use the context path in mockMvc tests. You only specify the #RequestMapping URI:
#Test
public void index() throws Exception {
this.mockMvc.perform(get("/business-case/1234").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("_links.business-cases", is(notNullValue())));
}

Spring MockMVC test with JSON content is throwing NullPointerException from Spring code

I have a small Spring MVC project. I'm writing MockMvc tests for it. Most of them work, but this one (the first one I've tried with a plain JSON body) is giving me trouble. I keep getting a NullPointerException from deep within Spring. I tried debugging through it, but eventually ran out of attached Spring source code without getting any closer to an answer.
My JSON block is captured from a live user test, which works fine. But in the test, it throws NPE. If I modify the JSON block to be malformed (IE, add an extra comma somewhere), then it throws a 400 Bad Request, as expected. Remove the extra comma, go back to NPE. Making the block invalid (IE, making a field null which is marked #NotNull in my domain object) does not give the expected 400 Bad Request. It just stays with the NPE.
All my other tests so far have been for controllers which just use query string params, and have worked fine. Also, I have one which due to browser restrictions on our customer's side must embed its JSON in a POST param (IE, "json = "{blah:blah}"), which I pull out and manually parse. That works fine, too.
Controller:
#RestController
public class SaveController {
#Autowired
private MyDao myDao;
#RequestMapping(value = "/path/to/controller", method = RequestMethod.POST)
#PreAuthorize("hasAnyRole('myRole', 'myAdminRole')")
public void updateThing(#Valid #RequestBody MyThing myThing) throws IOException {
myDao.updateMyThing(myThing);
}
}
Base test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = {TestDataAccessConfiguration.class, TestApplicationConfiguration.class})
public abstract class AbstractSpringTestCase {
#Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
MockitoAnnotations.initMocks(this);
}
}
Test:
public class SaveControllerTest extends AbstractSpringTestCase {
#Mock
private MyDao myDao;
#InjectMocks
#Autowired
private SaveController classUnderTest;
private static final JSON = "<a big JSON string captured from (working) production>";
#Test
public void testHappyPath() throws Exception {
mockMvc.perform(post("/path/to/controller")
.contentType(MediaType.APPLICATION_JSON)
.content(JSON))
.andExpect(status().isOk());
}
}
Stacktrace:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:170)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:137)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:145)
at SaveControllerTest.testHappyPath(SaveControllerTest.java)
In order to send a request body using Spring MockMvc you must map the #RequestBody object as a json string.
Here's an example:
SingupController:
#PostMapping("/signup")
public #ResponseBody
RealityKeeper createUser(#RequestBody SignupRequest signupRequest) {
System.out.println("SignupRequest: " + signupRequest);
String password = signupRequest.getPassword();
String username = signupRequest.getUsername();
String encoded = passwordEncoder.encode(password);
RealityKeeper realityKeeper = new RealityKeeper(username, encoded);
return repository.save(realityKeeper);
}
SignupControllerTest:
#Test
public void createUser() throws Exception {
SignupRequest signupRequest = new SignupRequest("foo", "bar");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String requestJson=ow.writeValueAsString(signupRequest);
mockMvc.perform(post("/api/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
.andExpect(MockMvcResultMatchers.status().isOk());
}
Using jackson's object mapper you can turn a pojo into a json string and pass it to the mockMvc content method.
Hope this helps

Categories