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());
}