It's impossible to create huge project without using unit tests. It seems that their popularity started with xUnit libraries family. Possibility to write hundreds of unit tests and ease of understanding results ("green bar") change the way we're programming. But what if we can't test our class using only JUnit?
1. Let's define the problem
Consider situation when our class is using other object method but due to some reasons we can't use that object in unit test.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public final class Publisher {
private final PlagiarismChecker plagiarismChecker;
public Publisher(PlagiarismChecker plagiarismChecker) {
this.plagiarismChecker = plagiarismChecker;
}
public void publishNewNovel(Writer writer) throws Plagiarism {
Novel novel = writer.getNovel();
plagiarismChecker.check(novel);
publish(novel);
}
private void publish(Novel novel) {
}
}
|
1
2
3
| public interface PlagiarismChecker {
void check(Novel novel) throws Plagiarism;
}
|
Our publisher wants to publish new novel. Unfortunately during tests we cannot check if novel is plagiarism. This operation is very long and needs access to database. For these reasons we cannot use default
PlagiarismChecker implementation in test environment.
2. Solution
2.1 Prerequisites
Firstly we have to design our class to use some kind of dependency injection. I don't order you to use Spring or Guice. My point is to destroy tight-coupling between classes. We have to have possibility to change object dependences.
2.2 Possible solutions
When we gain possibility to change object dependences we have to decide what to inject. There are three basic approaches. We can use:
- Stubs - they are the simplest implementation of some interface. We usually return hard-coded values.
Dilbert's comics is great example.
- Fakes - they are more advanced then Stubs. They offering some basic functionalities, but they are operating in simplified way. In our example PlagiarismChecker could compare novel title with some existing list.
- Mocks - helps with assertions. Can return hard-coded and calculated values. They are usually created by third-parties libraries.
We are going to try mocks using
Mockito.
2.3 Adding Mockito to project
We have to add dependency to w
pom.xml (i assume we are using Maven).
1
2
3
4
5
| <dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
|
That's all. Now we can use Mockito. In particular we can create mocks.
2.4 First mock
1
2
| PlagiarismChecker plagiarismCheckerMock = mock(PlagiarismChecker.class);
when(plagiarismCheckerMock.check(Matchers.any(Novel.class))).thenReturn(false);
|
This two lines have created fully "functional"
PlagiarismChecker object. When w invoke method check (for any
Novel) we will get
false. Short explanation:
- We used static Mockito function when. Mockito library was designed to be easy to read and understand. Consider when as begining of the sentence.
- We are passing argument to when method. In this case it is call to method check of plagiarismCheckerMock. To specify call arguments we are using Matchers.
- Finally we are saying what is going to happen after our call.
2.5 Final test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.mockito.Matchers;
public final class PublisherTest {
@Test
public void testGetNovel() throws Plagiarism {
PlagiarismChecker plagiarismCheckerMock = mock(PlagiarismChecker.class);
when(plagiarismCheckerMock.check(Matchers.any(Novel.class)))
.thenReturn(false);
Writer writerMock = mock(Writer.class);
when(writerMock.getNovel()).thenReturn(new Novel());
Publisher publisher = new Publisher(plagiarismCheckerMock);
publisher.publishNewNovel(writerMock);
}
}
|
What's important we are not testing Writer and PlagiarismChecker. Out test if fully independent.
3. Summary
I showed the simplest usage of Mockito. You need to remember that it's very powerful library. It offers: counting methods call, throwing exceptions, diffrent method results for diffrent parameters and many others.