Mocking frameworks

Our project looks cool, but it's too simple and it is far from being a real project. It still doesn't use external resources. A database is required by Java projects so we'll try to introduce it, as well.

What is the common way to test code that uses external resources or third-party libraries? Mocks are the answer. A mock object, or simply a mock, is a simulated object that can be used to replace real ones. They are very useful when objects that depend on external resources are deprived of them.

In fact, you don't need a database at all while you are developing the application. Instead, you can use mocks to speed up development and testing and use a real database connection only at runtime. Instead of spending time setting up a database and preparing test data, we can focus on writing classes and think about them later on during integration time.

For demonstration purposes, we'll introduce two new classes. The Person class and the FriendCollection class that are designed to represent persons and database object mapping. Persistence will be done with MongoDB (https://www.mongodb.org/).

Our sample will have two classes. Person will represent database object data; FriendCollection will be our data access layer. The code is, hopefully, self-explanatory.

Let's create and use the Person class:

public class Person {
    @Id
    private String name;

    private List<String> friends;

    public Person() { }

    public Person(String name) {
        this.name = name;
        friends = new ArrayList<>();
    }

    public List<String> getFriends() {
        return friends;
    }

    public void addFriend(String friend) {
        if (!friends.contains(friend)) friends.add(friend);
    }

}

Let's create and use the FriendsCollection class:

public class FriendsCollection {
    private MongoCollection friends;

    public FriendsCollection() {
        try {
            DB db = new MongoClient().getDB("friendships");
            friends = new Jongo(db).getCollection("friends");
        } catch (UnknownHostException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public Person findByName(String name) {
        return friends.findOne("{_id: #}", name).as(Person.class);
    }

    public void save(Person p) {
        friends.save(p);
    }
}

In addition, some new dependencies have been introduced so the Gradle dependencies block needs to be modified, as well. The first one is the MongoDB driver, which is required to connect to the database. The second is Jongo, a small project that makes accessing Mongo collections pretty straightforward.

The Gradle dependencies for mongodb and jongo are as follows:

dependencies {
    compile 'org.mongodb:mongo-java-driver:2.13.2'
    compile 'org.jongo:jongo:1.1'
}

We are using a database so the Friendships class should also be modified. We should change a map to FriendsCollection and modify the rest of the code to use it. The end result is the following:

public class FriendshipsMongo {
    private FriendsCollection friends;

    public FriendshipsMongo() {
        friends = new FriendsCollection();
    }

    public List<String> getFriendsList(String person) {
        Person p = friends.findByName(person);
        if (p == null)  return Collections.emptyList();
        return p.getFriends();
    }

    public void makeFriends(String person1, String person2) {
        addFriend(person1, person2);
        addFriend(person2, person1);
    }

    public boolean areFriends(String person1, String person2) {
        Person p = friends.findByName(person1);
        return p != null && p.getFriends().contains(person2);
    }

    private void addFriend(String person, String friend) {
        Person p = friends.findByName(person);
        if (p == null)  p  = new Person(person);
        p.addFriend(friend);
        friends.save(p);
    }
}

The complete source code can be found in the FriendsCollection and FriendshipsMongo classes in the https://bitbucket.org/vfarcic/tdd-java-ch02-example-junit.git repository.

Now that we have our Friendships class working with MongoDB, let's take a look at one possible way to test it by using mocks.

Mockito

Mockito is a Java framework that allows easy creation of the test double.

The Gradle dependency is the following:

dependencies {
  testCompile group: 'org.mockito', name: 'mockito-all', version: '1.+'
}

Mockito runs through the JUnit runner. It creates all the required mocks for us and injects them into the class with tests. There are two basic approaches; instantiating mocks by ourselves and injecting them as class dependencies via a class constructor or using a set of annotations. In the next example, we are going to see how is it done using annotations.

In order for a class to use Mockito annotations, it needs to be run with MockitoJUnitRunner. Using the runner simplifies the process because you just simply add annotations to objects to be created:

@RunWith(MockitoJUnitRunner.class)
public class FriendshipsTest {
...
}

In your test class, the tested class should be annotated with @InjectMocks. This tells Mockito which class to inject mocks into:

@InjectMocks
FriendshipsMongo friendships;

From then on, we can specify which specific methods or objects inside the class, in this case FriendshipsMongo, will be substituted with mocks:

@Mock
FriendsCollection friends;

In this example, FriendsCollection inside the FriendshipsMongo class will be mocked.

Now, we can specify what should be returned when friends is invoked:

Person joe = new Person("Joe");
doReturn(joe).when(friends).findByName("Joe");
assertThat(friends.findByName("Joe")).isEqualTo(joe);

In this example, we're telling Mockito to return the joe object whenever friends.findByName("joe") is invoked. Later on, we're verifying with assertThat that this assumption is correct.

Let's try to do the same test as we did previously in the class that was without MongoDB:

    @Test
    public void joeHas5Friends() {
        List<String> expected = Arrays.asList("Audrey", "Peter", "Michael", "Britney", "Paul");
        Person joe = spy(new Person("Joe"));

        doReturn(joe).when(friends).findByName("Joe");
        doReturn(expected).when(joe).getFriends();

        assertThat(friendships.getFriendsList("Joe"))
          .hasSize(5)
     .containsOnly("Audrey", "Peter", "Michael", "Britney", "Paul");
    }

A lot of things happened in this small test. First, we're specifying that joe is a spy. In Mockito, spies are real objects that use real methods unless specified otherwise. Then, we're telling Mockito to return joe when the friends method calls getFriends. This combination allows us to return the expected list when the getFriends method is invoked. Finally, we're asserting that the getFriendsList returns the expected list of names.

The complete source code can be found in the FriendshipsMongoAssertJTest class in the https://bitbucket.org/vfarcic/tdd-java-ch02-example-junit.git repository.

We'll use Mockito later on; throughout this book, you'll get your chance to become more familiar with it and mocking in general. More information about Mockito can be found at http://mockito.org/.

EasyMock

EasyMock is an alternative mocking framework. It is very similar to Mockito. However, the main difference is that EasyMock does not create spy objects but mocks. Other differences are syntactical.

Let's see an example of EasyMock. We'll use the same set of test cases as those that were used for Mockito examples:

@RunWith(EasyMockRunner.class)
public class FriendshipsTest {
    @TestSubject
    FriendshipsMongo friendships = new FriendshipsMongo();
    @Mock(type = MockType.NICE)
    FriendsCollection friends;

Essentially, the runner does the same as the Mockito runner:

@TestSubject
FriendshipsMongo friendships = new FriendshipsMongo();

@Mock(type = MockType.NICE)
FriendsCollection friends;

The @TestSubject annotation is similar to Mockito's @InjectMocks, while the @Mock annotation denotes an object to be mocked in a similar fashion to Mockito's @Mock. Furthermore, the type nice tells the mock to return empty.

Let's compare one of the asserts we did with Mockito:

    @Test
    public void mockingWorksAsExpected() {
        Person joe = new Person("Joe");
        expect(friends.findByName("Joe")).andReturn(joe);
        replay(friends);
        assertThat(friends.findByName("Joe")).isEqualTo(joe);
    }

Besides small differences in syntax, the only disadvantage of EasyMock is that the additional instruction replay was needed. It tells the framework that the previously specified expectation should be applied. The rest is almost the same. We're specifying that friends.findByName should return the joe object, applying that expectation and, finally, asserting whether the actual result is as expected.

In the EasyMock version, the second test method that we used with Mockito is the following:

    @Test
    public void joeHas5Friends() {
        List<String> expected = Arrays.asList("Audrey", "Peter", "Michael", "Britney", "Paul");
        Person joe = createMock(Person.class);

        expect(friends.findByName("Joe")).andReturn(joe);
        expect(joe.getFriends()).andReturn(expected);
        replay(friends);
        replay(joe);

        assertThat(friendships.getFriendsList("Joe"))
                .hasSize(5)
                .containsOnly("Audrey", "Peter", "Michael", "Britney", "Paul");
    }

Again, there are almost no differences when compared to Mockito, except that EasyMock does not have spies. Depending on the context, that might be an important difference.

Even though both frameworks are similar, there are small details that makes us choose Mockito as a framework, which will be used throughout this book.

Note

Visit http://easymock.org/ for more information about this asserts library.

The complete source code can be found in the FriendshipsMongoEasyMockTest class in the https://bitbucket.org/vfarcic/tdd-java-ch02-example-junit.git or https://github.com/TechnologyConversations/tdd-java-ch02-example-junit.git repositories.

Extra power for mocks

Both projects introduced above do not cover all types of methods or fields. Depending on the applied modifiers such static or final, a class, method, or field can be out of range for Mockito or EasyMock. In such as cases, we can use PowerMock to extend the mocking framework. This way, we can mock objects that can only be mocked in a tricky manner. However, one should be cautious with PowerMock since the necessity to use many of the features it provides is usually a sign of poor design. If you're working on a legacy code, PowerMock might be a good choice. Otherwise, try to design your code in a way that PowerMock is not needed. We'll show you how to do that later on.

For more information, visit https://code.google.com/p/powermock/.

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

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