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 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 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.
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.
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/.
18.223.196.146