Advanced Mockito

Controller Test

A way to capture the information going into the argument of the mock, in other words, to look at the value that is passed in.

When there is a class which takes in a property and then modifies that property.
Then we can do assertions to make sure that modification of the property which has been passed in is actually correct.

OwnerController
public class OwnerController {    private final OwnerService ownerService;    public String processFindForm(Owner owner, BindingResult result, Model model){        // allow parameterless GET request for /owners to return all records        if (owner.getLastName() == null) {            owner.setLastName(""); // empty string signifies broadest possible search        }        // find owners by last name        List<Owner> results = ownerService.findAllByLastNameLike("%"+ owner.getLastName() + "%");        if (results.isEmpty()) {            // no owners found            result.rejectValue("lastName", "notFound", "not found");            return "owners/findOwners";        } else if (results.size() == 1) {            // 1 owner found            owner = results.get(0);            return "redirect:/owners/" + owner.getId();        } else {            // multiple owners found            model.addAttribute("selections", results);            return "owners/ownersList";        }    }}
OwnerControllerTest
@ExtendWith(MockitoExtension.class)class OwnerControllerTest {    private static final String OWNERS_CREATE_OR_UPDATE_OWNER_FORM = "owners/createOrUpdateOwnerForm";    private static final String REDIRECT_OWNERS_5 = "redirect:/owners/5";    @Mock    OwnerService ownerService;    @InjectMocks    OwnerController controller;    @Mock    BindingResult bindingResult;    @Test    void processCreationFormHasErrors() {        //given        Owner owner = new Owner(1l, "Jim", "Bob");        given(bindingResult.hasErrors()).willReturn(true);        //when        String viewName = controller.processCreationForm(owner, bindingResult);        //then        assertThat(viewName).isEqualToIgnoringCase(OWNERS_CREATE_OR_UPDATE_OWNER_FORM);    }    @Test    void processCreationFormNoErrors() {        //given        Owner owner = new Owner(5l, "Jim", "Bob");        given(bindingResult.hasErrors()).willReturn(false);        given(ownerService.save(any())).willReturn(owner);        //when        String viewName = controller.processCreationForm(owner, bindingResult);        //then        assertThat(viewName).isEqualToIgnoringCase(REDIRECT_OWNERS_5);    }}

ArgumentCaptor

A way to capture the information going into the argument of the mock, in other words, to look at the value that is passed in.

When there is a class which takes in a property and then modifies that property.
Then we can do assertions to make sure that modification of the property which has been passed in is actually correct.

@ExtendWith(MockitoExtension.class)class OwnerControllerTest {    // ...    @Captor    ArgumentCaptor<String> stringArgumentCaptor;    @Test    void processFindFormWildcardString() {        //given        Owner owner = new Owner(1l, "Joe", "Buck");        List<Owner> ownerList = new ArrayList<>();        // Manually create a captor        final ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);        given(ownerService.findAllByLastNameLike(captor.capture())).willReturn(ownerList);        //when        String viewName = controller.processFindForm(owner, bindingResult, null);        //then        assertThat("%Buck%").isEqualToIgnoringCase(captor.getValue());    }    @Test    void processFindFormWildcardStringAnnotation() {        //given        Owner owner = new Owner(1l, "Joe", "Buck");        List<Owner> ownerList = new ArrayList<>();        // In an annotation way         given(ownerService.findAllByLastNameLike(stringArgumentCaptor.capture())).willReturn(ownerList);        //when        String viewName = controller.processFindForm(owner, bindingResult, null);        //then        assertThat("%Buck%").isEqualToIgnoringCase(stringArgumentCaptor.getValue());    }}

willAnswer

What willAnswer allows us to do is get a hold of the actual invocation of the mock and make some decisions about it.
It compares very closely to ArgumentCaptor but it's kind of combining what we are gonna return from the mock. So the answer functionality

OwnerController
public class OwnerController {    private final OwnerService ownerService;    public String processFindForm(Owner owner, BindingResult result, Model model){        // allow parameterless GET request for /owners to return all records        if (owner.getLastName() == null) {            owner.setLastName(""); // empty string signifies broadest possible search        }        // find owners by last name        List<Owner> results = ownerService.findAllByLastNameLike("%"+ owner.getLastName() + "%");        // Three different test scenarios        if (results.isEmpty()) {            // no owners found            result.rejectValue("lastName", "notFound", "not found");            return "owners/findOwners";        } else if (results.size() == 1) {            // 1 owner found            owner = results.get(0);            return "redirect:/owners/" + owner.getId();        } else {            // multiple owners found            model.addAttribute("selections", results);            return "owners/ownersList";        }    }}
OwnerControllerTest
@ExtendWith(MockitoExtension.class)class OwnerControllerTest {    @Mock    OwnerService ownerService;    @InjectMocks    OwnerController controller;    @Mock    BindingResult bindingResult;    @Captor    ArgumentCaptor<String> stringArgumentCaptor;    @BeforeEach    void setUp() {        given(ownerService.findAllByLastNameLike(stringArgumentCaptor.capture()))                .willAnswer(invocation -> {            List<Owner> owners = new ArrayList<>();            String name = invocation.getArgument(0);            // Three different test scenarios            if (name.equals("%Buck%")) {                owners.add(new Owner(1l, "Joe", "Buck"));                return owners;            } else if (name.equals("%DontFindMe%")) {                return owners;            } else if (name.equals("%FindMe%")) {                owners.add(new Owner(1l, "Joe", "Buck"));                owners.add(new Owner(2l, "Joe2", "Buck2"));                return owners;            }            throw new RuntimeException("Invalid Argument");        });    }    @Test    void processFindFormWildcardFound() {        //given        Owner owner = new Owner(1l, "Joe", "FindMe");        //when        String viewName = controller.processFindForm(owner, bindingResult, Mockito.mock(Model.class));        //then        assertThat("%FindMe%").isEqualToIgnoringCase(stringArgumentCaptor.getValue());        assertThat("owners/ownersList").isEqualToIgnoringCase(viewName);    }    @Test    void processFindFormWildcardStringAnnotation() {        //given        Owner owner = new Owner(1l, "Joe", "Buck");        //when        String viewName = controller.processFindForm(owner, bindingResult, null);        //then        assertThat("%Buck%").isEqualToIgnoringCase(stringArgumentCaptor.getValue());        assertThat("redirect:/owners/1").isEqualToIgnoringCase(viewName);    }        @Test    void processFindFormWildcardNotFound() {        //given        Owner owner = new Owner(1l, "Joe", "DontFindMe");        //when        String viewName = controller.processFindForm(owner, bindingResult, null);        //then        assertThat("%DontFindMe%").isEqualToIgnoringCase(stringArgumentCaptor.getValue());        assertThat("owners/findOwners").isEqualToIgnoringCase(viewName);    }}

Debugging the method @Test void processFindFormWildcardFound()

InOrder

Verify the order of the mock interactions,and specify that these mocks need to be called in the specific order.

    @Test    void processFindFormWildcardFound() {        //given        Owner owner = new Owner(1l, "Joe", "FindMe");        InOrder inOrder = inOrder(ownerService, model);        //when        String viewName = controller.processFindForm(owner, bindingResult, model);        //then        assertThat("%FindMe%").isEqualToIgnoringCase(stringArgumentCaptor.getValue());        assertThat("owners/ownersList").isEqualToIgnoringCase(viewName);        // inorder asserts        // To make sure that service is called before the model is called        inOrder.verify(ownerService).findAllByLastNameLike(anyString());        inOrder.verify(model).addAttribute(anyString(), anyList());    }