RushTheFox
RushTheFox

Reputation: 85

Why does Mockito return null when I specify an Optional of my type

I have a method on a controller to get a list of all the punishment types for a chat room (Kick, Ban, Warn and Mute). On the first test when I mock the data it works as expected and the test passes.

However, on my second test I provided. I defined what should be returned as an Optional<Punishment> with the attribute of punishmentName set as "mute". I am very confused why this is giving me null. When I run the Spring application outside of testing, the route works fine. For some reason the mock never wants to return the value I specified but only null. Specifically, this is being caught in the test on the line .andExpect(jsonPath("$.punishmentName", Matchers.equalTo("mute"))); as the fields value is null giving the following error:

java.lang.AssertionError: No value at JSON path "$.punishmentName"

For clarity, I have also provided the controller methods and service methods.

Punishment Controller Test:

@WebMvcTest(PunishmentController.class)
@RunWith(SpringRunner.class)
public class PunishmentControllerTest {

@Autowired
private MockMvc mvc;

@MockBean
private PunishmentService punishmentService;

@MockBean
private PunishmentValidator punishmentValidator;

@Test
public void getAllPunishmentTypesReturnsAListOfPunishmentTypes() throws Exception {
    List<Punishment> punishments = new ArrayList<>();
    punishments.add(new Punishment("mute"));
    punishments.add(new Punishment("kick"));
    punishments.add(new Punishment("ban"));
    punishments.add(new Punishment("warn"));

    Mockito.when(punishmentService.getAllPunishmentTypes()).thenReturn(punishments);

    mvc.perform(get("/api/punishments"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$", Matchers.hasSize(4)))
            .andExpect(jsonPath("$[0].punishmentName", Matchers.equalTo("mute")))
            .andExpect(jsonPath("$[1].punishmentName", Matchers.equalTo("kick")))
            .andExpect(jsonPath("$[2].punishmentName", Matchers.equalTo("ban")))
            .andExpect(jsonPath("$[3].punishmentName", Matchers.equalTo("warn")));
}

@Test
public void getPunishmentTypeReturnsMuteWhenMuteIsSpecified() throws Exception {
    Optional<Punishment> mute = Optional.of(new Punishment("mute"));
    Mockito.when(punishmentService.getPunishmentType("mute")).thenReturn(mute);

    mvc.perform(get("/api/punishments/mute"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.punishmentName", Matchers.equalTo("mute")));
}

Controller methods:

    /**
     * GET request for all punishment types.
     * @return List<Punishment> - When Punishments are found in the database they are returned in a List object.
     *                            Otherwise, an empty list is returned if no records are found or an error occurs.
     */
    @GetMapping
    public List<Punishment> getAllPunishments() {
        return punishmentService.getAllPunishmentTypes();
    }

    /**
     * GET request for one punishment type.
     * @param punishmentType String - The type of punishment.
     * @return Optional<Punishment> - The rule that gets returned or an empty optional if no rule is found.
     */
    @GetMapping(path = "{punishmentType}")
    public Optional<Punishment> getPunishment(@PathVariable("punishmentType") String punishmentType) {
        boolean isPunishmentTypeValid = punishmentValidator.validatePunishmentName(punishmentType);

        if (isPunishmentTypeValid) {
            return punishmentService.getPunishmentType(punishmentType);
        } else {
            return Optional.empty();
        }
    }
}

Service methods:

    /**
     * Gets all the punishment types
     * @return List<Punishment> - The rules in the community
     */
    public List<Punishment> getAllPunishmentTypes() {
        return punishmentRepository.findAll();
    }

    /**
     * Gets a specific punishment type.
     * @param punishmentType String - The type of punishment.
     * @return The punishment retrieved.
     */
    public Optional<Punishment> getPunishmentType(String punishmentType) {
        return punishmentRepository.findById(punishmentType);
    }

Upvotes: 1

Views: 3599

Answers (1)

Ken Chan
Ken Chan

Reputation: 90507

I believe it is because you forget to mock the method PunishmentValidator#validatePunishmentName("mute") to return true such that the method that you stub on PunishmentService is never invoked because by default if you do not stub a method , it will return false (see this).

Also it is a known behaviour that @MockBean is configured as lenient stubbing which will not reported error (i.e. throw UnnecessaryStubbingException) if you stub a method but it actually does not get executed.

So change the following should fix your problem :

@Test
public void getPunishmentTypeReturnsMuteWhenMuteIsSpecified() throws Exception {
    Optional<Punishment> mute = Optional.of(new Punishment("mute"));
    
    Mockito.when(punishmentService.getPunishmentType("mute")).thenReturn(mute);
    Mockito.when(punishmentValidator.validatePunishmentName("mute")).thenReturn(true);

    mvc.perform(get("/api/punishments/mute"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.punishmentName", Matchers.equalTo("mute")));
}

Upvotes: 3

Related Questions