In this recipe, we will take a look at a test that uses too many Mockito mocks. In this way, the test code becomes unreadable and unmaintainable. Since your test code is your living documentation, you should always remember to put a lot of effort into refactoring it until you can read it like a book.
For this recipe, we will again generate a new identity for a given person. Each person has an address, and that address has a street number. Since we are performing unit testing, we will check in isolation whether NewIdentityCreator
properly executes its logic. It is responsible for creating a new name, new street number, and new siblings for the current person, as shown in the following code:
class NewIdentityCreator { public String createNewName(Person person) { return person.getName() + "_new"; } public int createNewStreetNumber(Person person) { return person.getAddress().getStreetNumber() + 5; } public List<Person> createNewSiblings(Person person) { List<Person> newSiblings = new ArrayList<Person>(); for(Person sibling : person.getSiblings()) { Person newPerson = new Person(); person.setName(createNewName(sibling)); person.setAddress(sibling.getAddress()); person.setSiblings(sibling.getSiblings()); newSiblings.add(newPerson); } return newSiblings; } }
Let's assume that we already have a test that verifies this functionality. We will not go through all of the test cases, but we will focus on the functionality of generating new siblings as follows:
public class OverMockingNewIdentityCreatorTest { NewIdentityCreator systemUnderTest = new NewIdentityCreator(); @Test public void should_generate_new_siblings() { // given Person person = mock(Person.class); List<Person> oldSiblings = mock(List.class); given(person.getSiblings()).willReturn(oldSiblings); Iterator<Person> personIterator = mock(Iterator.class); given(oldSiblings.iterator()).willReturn(personIterator); given(personIterator.hasNext()).willReturn(true, true, true, false); given(personIterator.next()).willReturn(createPersonWithName("Amy"), createPersonWithName("John"), createPersonWithName("Andrew")); // when List<Person> newSiblings = systemUnderTest.createNewSiblings(person); // then then(newSiblings).isNotSameAs(oldSiblings); } private Person createPersonWithName(String name) { Person person = new Person(); person.setName(name); return person; } }
The preceding test is badly written because it violates a few of the good practices related to Mockito and testing such as:
Person
and List
classes created in the code that don't concern us. Imagine a case where the library that has either of those classes changes. Since we have its classes mocked, we will not see any difference and the tests will pass. Imagine what could happen on production once the real interactions take place instead of interactions between mocks—your application could crash. Another matter is that in order to use List
by using mocks, you have to perform plenty of stubbing. Once you look at such a test, you don't actually know what's going on any longer.NewIdentityCreator
class interacts with Person
and performs iteration over its elements (via the iterator of the list). As you can see in the test, we've mocked all collaborating objects and stubbed all possible interactions. The question that arises now is: do we really test production code if there are no real interactions any longer? Change your viewpoint and try not to mock if possible.To properly rewrite a test that uses too many mocks, you have to perform the following steps:
Person
, List
, and Iterator
classes)Person
class and create an ordinary List
)Let's assume that we want to extract the Person
object creation into a separate class to make the test code more readable (see the code repository on GitHub for exact implementation details). Take a look at the following code:
public class PersonBuilder { private String name; private Address address; private List<Person> siblings; public PersonBuilder name(String name) { this.name = name; return this; } public PersonBuilder address(Address address) {…} public PersonBuilder streetNumber(int streetNumber) {…} public PersonBuilder siblings(List<Person> siblings) {… } public Person build() { Person person = new Person(); person.setName(name); person.setAddress(address); person.setSiblings(siblings); return person; } public static PersonBuilder person() { return new PersonBuilder(); } }
As you can see in the previous code, the PersonBuilder
class has a static factory method to instantiate itself. It allows you to use less code and more ubiquitous language (check Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans, available at http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) in your tests. It has fields that are filled up with data during the building process. The Person
class is created upon the build()
method execution. For the sake of readability, we will not go into the details of AddressBuilder
, but it follows the same pattern and sets a street address on the Address
object.
Now that we have the builder ready, we can use it to rewrite the test as follows:
public class NewIdentityCreatorTest { NewIdentityCreator systemUnderTest = new NewIdentityCreator(); @Test public void should_generate_new_siblings() { // given List<Person> oldSiblings = createSiblings(); Person person = createPersonWithStreetNumberAndSiblingsAndName(oldSiblings); // when List<Person> siblings = systemUnderTest.createNewSiblings(person); // then then(siblings).doesNotContainAnyElementsOf(oldSiblings); } private Person createPersonWithStreetNumberAndSiblingsAndName(List<Person> siblings) { return person().streetNumber(10) .siblings(siblings) .name("Robert") .build(); } private List<Person> createSiblings() { return asList( person().name("Amy").build(), person().name("John").build(), person().name("Andrew").build() ); } }
Now, the test looks much better and it can be used as a living documentation of your application.
When working with legacy code or third-party software, you can come across very deeply nested structures that row in hundreds of lines of code. You don't own these value objects, so you can't change it in any way. Using these objects may be tedious, so it's important to have proper factory methods/builders to create them.
Of course, context is king and there might be cases in which a more pragmatic approach will be to not build the whole object using builders but to stub a very precisely defined chain of method execution. In Mockito, such stubbing is called deep stubbing, and the Answer
implementation that allows you to set up such stubbing behavior is called Mockito.RETURNS_DEEP_STUBS
. The chain violates the Law of Demeter (see Object-Oriented Programming: An Objective Sense of Style, K. Lieberherr, I. IIolland, A. Riel, available at http://www.ccs.neu.edu/research/demeter/papers/law-of-demeter/oopsla88-law-of-demeter.pdf.) since we're breaking the the friend of my friend is not my friend rule. Remember that you really need some legitimate reasons to use deep stubbing. In a well-designed codebase, you will not need to perform such actions. For educational purposes, let's take a look at the usage of deep stubs. We test the creation of a street number, where, in order to get it, we have to pass it through the Person.getAddress().getStreetNumber()
chain of method execution, as shown in the following code:
public class DeepStubbingNewIdentityCreatorTest { NewIdentityCreator systemUnderTest = new NewIdentityCreator(); @Test public void should_generate_new_address_with_street_number() { // given Person person = mock(Person.class, RETURNS_DEEP_STUBS); given(person.getAddress().getStreetNumber()).willReturn(10); // when int newStreetNumber = systemUnderTest.createNewStreetNumber(person); // then then(newStreetNumber).isNotEqualTo(person.getAddress().getStreetNumber()); } }
When calling given(person.getAddress().getStreetNumber()).willReturn(10)
under the hood, Mockito creates all the intermediary mocks. In this case, since Person
is already a mock, Address
is also created as a mock. In this way NullPointerException
is not thrown when moving down the chain of method invocations. Remember that it is not a sign of good design when you need to use this feature of Mockito.
3.145.39.60