Hot questions for Using Mockito in spring mvc test

Top 10 Java Open Source / Mockito / spring mvc test

Question:

I've been trying to figure out why my mocked findIngredientsByCategory method is returning null when I have when(controller.findIngredientsByCategory(any()).thenReturn(Collections.emptyList()). This implementation works for the findAll method works.

Below is my implementation for my unit test:

@RunWith(SpringJUnit4ClassRunner.class)
@WebMvcTest(IngredientController.class)
@ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
@WebAppConfiguration
public class IngredientControllerTest {

  @Autowired
  private WebApplicationContext context;

  @Autowired
  private MockMvc mvc;

  @MockBean
  private IngredientController ingredientController;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);
    mvc = MockMvcBuilders.webAppContextSetup(context).build();
  }

  @Autowired
  private ObjectMapper mapper;

  private static class Behavior {
    IngredientController ingredientController;

    public static Behavior set(IngredientController ingredientController) {
      Behavior behavior = new Behavior();
      behavior.ingredientController = ingredientController;
      return behavior;
    }

    public Behavior hasNoIngredients() {
      when(ingredientController.getAllIngredients()).thenReturn(Collections.emptyList());
      when(ingredientController.getIngredientsByCategory(any())).thenReturn(Collections.emptyList());
      when(ingredientController.getIngredientById(anyString())).thenReturn(Optional.empty());
      return this;
    }
  }

  @Test
  public void getIngredientsByCategoryNoIngredients() throws Exception {
    Behavior.set(ingredientController).hasNoIngredients();
    MvcResult result = mvc.perform(get("/ingredients/filter=meat"))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
        .andReturn();
    String content = result.getResponse().getContentAsString();
    System.out.println(content);
 }

And below is the implementation for the controller:

@RestController
@RequestMapping("/ingredients")
public class IngredientController {

  @Autowired
  private IngredientRepository repository;

  @RequestMapping(value = "/filter", method = RequestMethod.GET)
  public List getIngredientsByCategory(@RequestParam("category") String category) {
    return repository.findByCategory(category);
  }
}

I'm not sure why the mock controller is returning null with this request, when I tell it to return an empty list. If someone could please help with this I would greatly appreciate it! Thanks.


Answer:

The MockMvc actually will call the IngredientController that is bootstrapped and created by the Spring Test framework but not call the mocked IngredientController that you annotated with @MockBean, so all the stubbing that you made will not be called.

Actually, the point of @WebMvcTest is to test @RestController and its related Spring configuration is configured properly , so a real instance of IngredientController is necessary to create rather than using a mocked one. Instead , you should mock the dependencies inside IngredientController (i.e IngredientRepository).

So , the codes should looks like:

@RunWith(SpringJUnit4ClassRunner.class)
@WebMvcTest(IngredientController.class)
@ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
@WebAppConfiguration
public class IngredientControllerTest {

  @Autowired
  private WebApplicationContext context;

  @Autowired
  private MockMvc mvc;

  @MockBean
  private IngredientRepository ingredientRepository;


  @Test
  public void fooTest(){
    when(ingredientRepository.findByCategory(any()).thenReturn(Collections.emptyList())

    //And use the MockMvc to send a request to the controller, 
    //and then assert the returned MvcResult
  }

}

Question:

I am trying to test my controller endpoint and my requestbody annotated with @Valid annotation. My Testclass looks like the follow:

@RunWith(SpringRunner.class)
@WebMvcTest(value = BalanceInquiryController.class, secure = false)
public class BalanceInquiryControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BalanceInquiryController balanceInquiryController;

    @Test
    public void testGetBalanceInquiry() throws Exception {
        RequestBuilder requestBuilder = MockMvcRequestBuilders
                .post("/com/balanceInquiry")
                .accept(MediaType.APPLICATION_JSON)
                .content("{\"comGiftCard\":{\"cardNumber\":\"1234567890\",\"pinNumber\":\"0123\"},\"comMerchant\":\"MERCHANT1\"}")
                .contentType(MediaType.APPLICATION_JSON);

        MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();
        assertEquals(HttpStatus.OK.value(), response.getStatus());
    }
}

My Controller - @PostMapping looks like that:

@PostMapping(value = "/com/balanceInquiry")
public ResponseEntity<?> getBalanceInquiry(@Valid @RequestBody BalanceInquiryModel balanceInquiry, Errors errors) {
    if (errors.hasErrors()) {
        return new ResponseEntity<String>("Validation error", HttpStatus.BAD_REQUEST);
    }
    //do any stuff...
    return new ResponseEntity<BalanceInquiryResponse>(balanceInquiryResponse, HttpStatus.OK);
}

My BalanceInquiryModel is annotated with @Valid and has some hibernate and custom validations behind. Those validations are all ok and already unit tested.

What I like to test is my endpoint where I send a valid json request body expecting a 200 response and also an invalid json request body expecting a 400 response validated by the set @Valid implementation.

For example an unvalid call is to send no pinNumber or length < 4.

I have read some threads and some uses MockMvcBuilders.standaloneSetup() to mock the full controller. But I wont do a full integration test.

Not quite sure how to go on with this situation and if I should go on.

P.S.: At the moment I get always a 200 response no matter if the validation should give an error or not.

Here a gist for more code and the validation classes/models.


Answer:

Here's one of my example I work on my project hope it help you out:

I have a global exception handler to handler my MethodArgumentNotValidException and throw it

@RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {

        User savedUser = userService.save(user);

        return new ResponseEntity<User>(savedUser, HttpStatus.CREATED);
    }


public void testAdduser() throws Exception{
        final User request = new User();
        request.setFirstName("Test");
        request.setLastName("some description");

        mockMvc.perform(post(END_POINT+"/add")
                .contentType(MediaType.APPLICATION_JSON)
                .content(stringify(request))

        ).andDo(print()).andExpect(status().isUnprocessableEntity())
        ;
    }
private String stringify(Object object) throws JsonProcessingException {
        return new ObjectMapper().writeValueAsString(object);
    }

Update:

I think your main problem is that you are using @WebMvcTest in stead of @SpringBootTest.

the different between 2 of them is that:

@SpringBootTest annotation will loads complete application and injects all the beans which is can be slow.

@WebMvcTest - for testing the controller layer. it doesn't inject other bean beside the @RestController

so if you are just testing just pure controller to see u can reach the endpont then you can just use @WebMvcTest which will make your test run faster.

but in your case, you want it to run the spring validation, you will need to use @SpringBootTest

for detailed: https://spring.io/guides/gs/testing-web/

Question:

My Mock call is like below :

BDDMockito.given(restTemplate.exchange(url, 
    HttpMethod.POST, 
    BDDMockito.any(), 
    Response.class)
).willReturn(responseEntity);

but i am getting below errors . please help me out on this???????

4 matchers expected, 1 recorded:
-> at com.esrx.aggregation.service.servicecaller.GetSpecalityInventoryItemsCallerTest.getSpecalityInventoryItemsCallPositiveTest(GetSpecalityInventoryItemsCallerTest.java:67)

This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
    //correct:
    someMethod(anyObject(), eq("String by matcher"));

For more info see javadoc for Matchers class.
, mergedContextConfiguration = [WebMergedContextConfiguration@61375dff testClass = GetSpecalityInventoryItemsCallerTest, locations = '{}', classes = '{class com.esrx.aggregation.application.Main, class com.esrx.aggregation.application.Main}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{classpath:/application.properties, classpath:/service.properties}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.SpringBootTestContextCustomizer@351d00c0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@35d019a3, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@78691363], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]].
2017-12-19 15:59:53.085  INFO 10140 --- [       Thread-5] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@5a62b2a4: startup date [Tue Dec 19 15:59:35 IST 2017]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@3e6f3f28

Answer:

That's because you can't mix matchers with raw values. If you use matchers then you need to use matchers for all arguments. If you still want to match an exact value you can use the .eq() matcher:

BDDMockito.given(restTemplate.exchange(Mockito.eq(url), Mockito.eq(HttpMethod.POST), BDDMockito.any(), Mockito.eq(Response.class))).willReturn(responseEntity)