3

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.

baron.jos
  • 55
  • 1
  • 1
  • 5

2 Answers2

1

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
  }

}
Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • How do you instantiate the controller with this implementation? – baron.jos Nov 01 '19 at 04:02
  • `SpringJUnit4ClassRunner` will start the spring context which in turn create the controller under the cover . Just like you start the spring application normally which you do not need to create the controller by yourself . – Ken Chan Nov 01 '19 at 04:04
  • 1
    This was super helpful in learning how mocking works, so thanks! – baron.jos Nov 01 '19 at 04:07
1

Th request path in test is "/ingredients/filter=meat", but it should be "/ingredients/filter?category=meat". So, it seem that getIngredientsByCategory was not called.

ilinykhma
  • 980
  • 1
  • 8
  • 14