- can't mock static classes
- can't mock final classes
- can't mock static methods
- can't mock equals and hashCode
So, lets go for a quick tour.
First Sample
As usual, we create a DAO interface, thtat is used by a service:
public interface SampleDao {
Sample findByPrimaryKey(Long id);
}
public class SampleService {
private SampleDao sampleDao;
public void setSampleDao(SampleDao sampleDao) {
this.sampleDao = sampleDao;
}
public boolean exists(Long id) {
return sampleDao.findByPrimaryKey(id) != null;
}
}
To test our service, we need to create a mock of the DAO:
public class SampleServiceTest {
private SampleService sampleService = new SampleService();
private SampleDao sampleDao;
@Before
public void setUp() throws Exception {
if(sampleDao == null) {
sampleDao = Mockito.mock(SampleDao.class);
sampleService.setSampleDao(sampleDao);
}
}
}
Creating a mock is simply done by calling mock method on a class object. Mockito can mock interfaces, it is the minimum for a mocking framework, but it can also mock classes in its standard edition. For example, EasyMock can mock classes, but an extension must be used to do so. When an object is a mock, if no behavior is specified, methods return the default return type value.
Our test is quiet simple, and the return value of findByPrimaryKey
is correct. But how can we be sure our DAO code is called. Mockito provides an easy way to check.
public void testExists() {
Random random = new Random(System.currentTimeMillis());
Long id = random.nextLong();
boolean result = sampleService.exists(id);
Mockito.verify(sampleDao).findByPrimaryKey(id);
Assert.assertFalse(result);
}
By this way, Mockito check if the method findByPrimaryKey
has been called only one time, and with id as parameter. If no verify is implemented in test, no check is done on the method execution. So, the order in which the behavior is implemented does not have any importance, and mock building is uncoupled to the method call stack.
Implementing behavior
Implementing the behavior is called stubbing in Mockito. In our previous sample, the mocked DAO uses the default behavior, i.e. findByPrimaryKey
always return null. But how to configure the DAO for a call to exists return true?
public void testExists() {
Random random = new Random(System.currentTimeMillis());
Long id = random.nextLong();
Mockito.when(sampleDao.findByPrimaryKey(id)).thenReturn(new Sample());
boolean result = sampleService.exists(id);
Mockito.verify(sampleDao).findByPrimaryKey(id);
Assert.assertTrue(result);
}
It is very simple to implement. Method when encapsulate a call to the stubbed method, and method thenReturn
provides the object to return on a method call. By stubbing the method this way, we say that a new Sample
object is returned for this specific id value. In our case, there is no importance, because we produce this id. But in other case we don't known the id value, Mockito provides us way to implement more complexe behavior depending of the passed arguments.
We can also throw an exception on a method call. For example, we can suppose our findByPrimaryKey
does not return null when no data has been found, but throw a EntityNotFoundException
(JPA API). Our exists methods becomes:
public boolean exists(Long id) {
try {
sampleDao.findByPrimaryKey(id);
} catch(EntityNotFoundException e) {
return false;
}
return true;
}
And the test:
public void testExistsNotFound() {
Random random = new Random(System.currentTimeMillis());
Long id = random.nextLong();
Mockito.when(sampleDao.findByPrimaryKey(id)).thenThrow(new EntityNotFoundException());
boolean result = sampleService.exists(id);
Mockito.verify(sampleDao).findByPrimaryKey(id);
Assert.assertFalse(result);
}
Now, we have two tests. Each one implements its own behavior on the DAO. To unregister behavior on mock, we have to reset it:
@After
public void tearDown() throws Exception {
if(dao != null) {
Mockito.reset(sampleDao);
}
}
Deal with void return method
Now, we know how to stub a method with a non void return, but how to implement behavior on this kind of method. To do it, we add a method to our DAO interface:
public interface SampleDao {
Sample findByPrimaryKey(Long id);
void update(Sample sample);
}
To mock it, we have to change a little the stubbing syntax. In place of writing:
when(mock.call_to_the_method).then()
The syntax becomes:
doNothing().when(mock).call_to_the_method
The standard behavior of an update method, is to call a findByPrimaryKey
method to retrive tha actual stored bean values. In case of no bean has been found, the EntityNotFoundException
is thrown.
To implement the case of update success:
Mockito.doNothing().when(dao).update(new Sample());
To implement the case of update failure:
Mockito.doThrow(new EntityNotFoundException()).when(dao).update(new Sample());
Advanced Stubbing
Until now, we have implemented simple stubbing. So, it is time to look at advanced stubbing, and to do so, we take our two test methods on exists and turn them in one, and we implements a behavior, which is able to throw an exception or return something depending of the parameter. I'm agree: if I do that, I can't check service return, but this is only for example, so be indulgent.
public void testExists() {
Random random = new Random(System.currentTimeMillis());
Long id = random.nextLong();
Mockito.when(sampleDao.findByPrimaryKey(id)).thenAsnwer(
new Answer<Sample>() {
@Override
public Sample answer(InvocationOnMock invocation) {
Long id = (long) Invocation.getParameters()[0];
if (id > 10000) {
throw new EntityNotFoundException();
} else {
return new Sample();
}
}
});
sampleService.exists(id);
Mockito.verify(sampleDao).findByPrimaryKey(id);
}
Now, is id
is greater than 10000, an exception is thrown, else, a new Sample
object is created. Answer
is an easy way to implements behavior depending of method parameters. But, its usage must not be systematic. Then().do...() syntax should be mostly used.
For now, we have to specify for which id, our behavior is implemented. But we implements that for any id, isn't it? Mockito comes with matchers to express this kind of constraints:
public void testExists() {
Random random = new Random(System.currentTimeMillis());
Mockito.when(sampleDao.findByPrimaryKey(Mockito.anyLong())).thenAsnwer(
new Answer() {
@Override
public Sample answer(InvocationOnMock invocation) {
Long id = (long) Invocation.getParameters()[0];
if (id > 10000) {
throw new EntityNotFoundException();
} else {
return new Sample();
}
}
});
sampleService.exists(random.nextLong());
Mockito.verify(sampleDao).findByPrimaryKey(Mockito.anyLong());
}
WARNING: it is not possible to use matcher on only one stubbed method parameter. Each method parameter must use matcher, else, Mockito throws a InvalidUseOfMatchersException
.
It is finished for this quick tour. For furthur, go to the Mockito documentation page and have a look to documentation.
Good stuff mate ....TA
ReplyDelete