Removing the problems with instance creation

In this recipe, we will first test an existing class that uses new to instantiate an object which performs complex logic; then, we'll refactor it. The problem with the new operator is that it's very difficult to mock the created instance. An object's collaborators should be passed as parameters of the constructor or somehow injected via the dependency injection system.

Getting ready

Let's assume that our system under test is a system that generates a new identity for a given person who can have a name, an age, and siblings. Note that the following snippet presents a poorly designed class:

public class BadlyDesignedNewPersonGenerator {

    public Person generateNewIdentity(Person person) {
        NewIdentityCreator newIdentityCreator = new NewIdentityCreator();
        String newName = newIdentityCreator.createNewName(person);
        int newAge = newIdentityCreator.createNewAge(person);
        List<Person> newSiblings = newIdentityCreator.createNewSiblings(person);
        return new Person(newName, newAge, newSiblings);
    }
    
}

In the preceding code, the NewIdentityCreator class performs the following logic (for simplicity, we only write that part of the code where we are accessing external resources):

class NewIdentityCreator {

    static final String DEFAULT_NEW_NAME = "NewName";

    public String createNewName(Person person) {
        System.out.printf("Calling web service and creating new name for person [%s]%n", person.getName());
        return DEFAULT_NEW_NAME;
    }

    public int createNewAge(Person person) {
        System.out.printf("Calling db and creating new age for person [%s]%n", person.getName());
        return person.getAge() + 5;
    }

    public List<Person> createNewSiblings(Person person) {
        System.out.printf("Making heavy IO operations andcreating new siblings for person [%s]%n", person.getName());
        return Arrays.asList(new Person("Bob"),new Person("Andrew"));
    }

}

How to do it...

In order to test the preceding implementation and then refactor it, we have to perform the following steps:

  1. Write a test that verifies the behavior of the system under test (in this case, we want to be sure that the person has a new identity at the end of the day).
  2. Mock out all of the collaborators, if necessary (if you know what you are doing, you can move to the next point without performing this intermediary step).
  3. Refactor the code so that it follows good coding practices, including the SOLID principles.

How it works...

We start by writing a test which will verify that our application works as expected. This will give us confidence for further refactoring. Object instantiation in the method's body leads to heavy coupling between the BadlyDesignedNewPersonGenerator and NewIdentityCreator classes. What we want to achieve is component isolation, decoupling in other words. Once both the classes are decoupled, they can be tested in isolation, which makes the tests smaller and less complex.

In the following code snippet, you will find a test for your system that verifies whether the BadlyDesignedNewPersonGenerator class will generate a new identity for the given person. It references step 1 of the How to do it... section of this recipe. All the examples are presented for JUnit and AssertJ; please refer to Chapter 1, Getting Started with Mockito, for TestNG configuration and Chapter 7, Verifying Behavior with Object Matchers, for AssertJ configuration and the BDDAssertions static imports:

public class BadlyDesignedNewPersonGeneratorTest {

    BadlyDesignedNewPersonGenerator systemUnderTest = new BadlyDesignedNewPersonGenerator();

    @Test
    public void should_return_person_with_new_identity() {
        // given
        List<Person> siblings = asList(new Person("John", 10), new Person("Maria", 12));
        Person person = new Person("Robert", 25, siblings);

        // when
        Person newPerson = systemUnderTest.generateNewIdentity(person);

        // then
        then(newPerson).isNotEqualTo(person);
        then(newPerson.getAge()).isNotEqualTo(person.getAge());
        then(newPerson.getName()).isNotEqualTo(person.getName());
        then(newPerson.getSiblings()).doesNotContainAnyElementsOf(siblings);
    }
  
} 

Now that we have the test, let's move to step 2 of the How to do it... section of this recipe. We have to stub the method interactions that access external resources. We will try to mock out the existing object initialization and replace it with a mock by using PowerMock, as shown in the following code (remember that this should never happen in properly written code):

@RunWith(PowerMockRunner.class)
@PrepareForTest(BadlyDesignedNewPersonGenerator.class)
public class BadlyDesignedNewPersonGeneratorPowerMockTest {

    BadlyDesignedNewPersonGenerator systemUnderTest =new BadlyDesignedNewPersonGenerator();

    @Test
    public void should_return_person_with_new_identity() throws Exception {
        // given
        List<Person> siblings = asList(new Person("John", 10), new Person("Maria", 12));
        Person person = new Person("Robert", 25, siblings);
        NewIdentityCreator newIdentityCreator = Mockito.mock(NewIdentityCreator.class);
        PowerMockito.whenNew(NewIdentityCreator.class).withAnyArguments().thenReturn(newIdentityCreator);

        // when
        Person newPerson = systemUnderTest.generateNewIdentity(person);

        // then
        then(newPerson).isNotNull().isNotEqualTo(person);
        then(newPerson.getAge()).isNotEqualTo(person.getAge());
        then(newPerson.getName()).isNotEqualTo(person.getName());
        then(newPerson.getSiblings()).doesNotContainAnyElementsOf(siblings);
    }
}

Note

As you can see in the previous code, we do not stub methods on the NewIdentityCreator class so that they return any particular values. The default Mockito ones are okay for us—we just want to be sure that the input and resulting person are not the same. The YAGNI principle (the You Aren't Gonna Need it principle defined in Extreme Programming Installed, Ronald E. Jeffries, Ann Anderson, and Chet Hendrickson) makes sense here. We don't care about concrete values; we only want to be sure that the person's identity has changed.

Now that we have the test, we can refactor the code and remove any PowerMock occurrence since it only proves that our code is full of bad ideas. The following is the snippet that shows the refactored version of our BadlyDesignedNewPersonGenerator class. We perform step 3 of the How to do it... section of this recipe. This time, NewIdentityCreator is injected through the constructor as a dependency. Check the Inversion of Control Containers and the Dependency Injection pattern article by Martin Fowler, available at http://www.martinfowler.com/articles/injection.html, for more information on dependency injection.

public class RefactoredNewPersonGenerator {

    private final NewIdentityCreator newIdentityCreator;

    public RefactoredNewPersonGenerator(NewIdentityCreator newIdentityCreator) {
        this.newIdentityCreator = newIdentityCreator;
    }

    public Person generateNewIdentity(Person person) {
        String newName = newIdentityCreator.createNewName(person);
        int newAge = newIdentityCreator.createNewAge(person);
        List<Person> newSiblings = newIdentityCreator.createNewSiblings(person);
        return new Person(newName, newAge, newSiblings);
    }
}

Finally, we can refactor the test by removing all of the PowerMock dependencies and by proper injection of the RefactoredNewPersonGenerator collaborator, NewIdentityCreator. Remember that the actual logic of the test hasn't changed at all. Take a look at the following code:

@RunWith(MockitoJUnitRunner.class)
public class RefactoredPersonGeneratorTest {

    @Mock NewIdentityCreator newIdentityCreator;

    @InjectMocks RefactoredNewPersonGenerator systemUnderTest;

    @Test
    public void should_return_person_with_new_identity() {
        // given
        List<Person> siblings = asList(new Person("John", 10), new Person("Maria", 12));
        Person person = new Person("Robert", 25, siblings);

        // when
        Person newPerson = systemUnderTest.generateNewIdentity(person);

        // then
        then(newPerson).isNotNull().isNotEqualTo(person);
        then(newPerson.getAge()).isNotEqualTo(person.getAge());
        then(newPerson.getName()).isNotEqualTo(person.getName());
        then(newPerson.getSiblings()).doesNotContainAnyElementsOf(siblings);
    }
}

There's more...

Here's another example of the same type of refactoring. You may have had issues with mocking time in your applications. You had to see this new Date() instantiation passed around in your code, and you wondered how to deal with it in your test. Let's assume that we have a class which logs the time of a visit of a person and returns that time, as shown in the following code:

public class BadlyDesignedVisitLogger {

    public Date logUsersVisit(){
        Date dateOfLogging = new Date();
        System.out.printf("User visited us at [%s]%n", dateOfLogging);
        return dateOfLogging;
    }

}

Since you already know how to extract responsibilities to separate classes, you can think of the new Date() instantiation as a responsibility of a certain class presented as follows (of course, at some point you will have to test that extracted class, but it will be a relatively trivial test):

public class RefactoredVisitLogger {

    private final TimeSource timeSource;

    public RefactoredVisitLogger(TimeSource timeSource) {
        this.timeSource = timeSource;
    }

    public Date logUsersVisit(){
        Date dateOfLogging = timeSource.getDate();
        System.out.printf("User visited us at [%s]%n", dateOfLogging);
        return dateOfLogging;
    }

}

The TimeSource class can be mocked, as shown in the following code:

public class TimeSource {

  static final String DATE_FORMAT = "dd-MM-yyyy";

  public Date getDate() {
        return new Date();
    }
  
  public static Date on(String date) {
    SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
    try {
      return formatter.parse(date);
    } catch (ParseException e) {
      throw new InvalidDateFormatException(e);
    }
  }

}

Now, the test for such a class for JUnit will look as follows (please refer to Chapter 1, Getting Started with Mockito, for the TestNG configuration and Chapter 7, Verifying Behavior with Object Matchers, for AssertJ configuration and BDDAssertions static imports):

@RunWith(MockitoJUnitRunner.class)
public class VisitLoggerTest {

    @Mock TimeSource timeSource;

    @InjectMocks RefactoredVisitLogger refactoredVisitLogger;

    @Test
    public void should_return_users_logging_time() {
        // given
        Date currentDate = new Date();
        given(timeSource.getDate()).willReturn(currentDate);

        // when
        Date dateOfLogging = refactoredVisitLogger.logUsersVisit();

        // then
        then(dateOfLogging).isSameAs(currentDate);
    }

}

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.62.122