Migration to Mockito 2.1
11 Oct 2016Recently we got a great news.
They released Mockito 2.1.0 - after so many years of hopeless waiting!
There is a lot of cool updates in Mockito 2, including:
- Support for Java 8
- Migration from CGLIB to ByteBuddy
- Mocking final classes and methods
Yes! I must use it! - I decided.
What a huge disappointment it was for me…
Epic fail
I tried to upgrade my working project to Mockito 2.1.0, and … ups.
I got over 100 red tests (from ~6000).
My first emotion was - what the hell! Mockito 2 sucks!
But the further investigation showed that Mockito 2 is cool, but some of my tests are bad. The new Mockito found a whole bunch of problems in our tests.
Let’s discover them together.
What you need to do
Below you can find my tutorial for migration to Mockito 2.
Let’s start!
First, you will need to replace import
A lot of imports. In many files. Replace these lines:
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyVararg; // not needed - replaced by any()
by these lines:
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
Second, you will update API of doAnswer
Was:
when(userDeviceService.save(any(UserDevice.class)))
.then(invocation -> invocation.getArgumentAt(0, UserDevice.class));
Now:
when(userDeviceService.save(any()))
.then(invocationOnMock -> invocationOnMock.getArgument(0));
(getArgumentAt
-> getArgument
, remove the class)
Third, you update API of isMock
Was:
import org.mockito.internal.util.MockUtil;
assertFalse(new MockUtil().isMock(expected));
Now:
import static org.mockito.internal.util.MockUtil.isMock;
assertFalse(isMock(expected));
The last version is really better, don’t you think? No need to create an unused object.
What problems you will experience
Some of your tests will break when you upgrade to Mockito 2.1. Below you will find some of the reasons.
1) Matcher any()
does not match null
anymore
This code worked before:
doNothing().when(service).calculateAmounts(any(BigDecimal.class), anyString());
But doesn’t work anymore, if some of parameters are null
.
Yes, my friend! You will need to rework all your tests that pass null
instead of realistic value.
And it’s great, because the new code is really better:
doNothing().when(service).calculateAmounts(order.amount, order.currency);
Or this way:
doNothing().when(service).calculateAmounts(new BigDecimal("100.00"), "EEK");
If you really need to pass null
parameter, you can still do it.
Just pass null explicitly using isNull
matcher:
doNothing().when(service).calculateAmounts(any(BigDecimal.class), isNull());
2) Matcher anyInt() does not match long anymore
This code worked with Mockito 1.x, but fails with Mockito 2.x:
when(mqService.send(anyString(), anyInt())).thenReturn("transfer-ref");
You need to replace anyInt()
by anyLong()
for Mockito 2.1:
when(mqService.send(anyString(), anyLong())).thenReturn("transfer-ref");
Yes, my friend. You will need to rework all your tests that pass int
instead of long
etc.
But it’s great, don’t you think?
Because these tests were inaccurate.
3) You will discover bad tests
Yes, my friend. You will discover lot of bad lines among your tests. Just bad. Like this one:
@Test
public void checkMoratoriumRunsSilentlyWhenNoMoratorium() {
doReturn("false").when(service).parseMoratoriumMessage(any(Mandate.class), any(LoanApplication.class));
...
service.checkForMoratorium(any(Mandate.class), any(LoanApplication.class)); // WHAAAAAAAT?
...
}
This code worked with Mockito 1.x. Suddenly. But it fails with Mockito 2.1 - and it’s great, don’t you think?
Obviously, we should use mock
instead of any
in the second line:
service.checkForMoratorium(mock(Mandate.class), mock(LoanApplication.class));
By the way, even better solution is to avoid both mock
and any
and just create plain objects:
service.checkForMoratorium(new Mandate(), new LoanApplication());
4) You will discover a lot of sloppy tests
… that check only some parameters and don’t discover that others are null
.
For example:
doReturn(user).when(loginService).tokenLogin(eq("bob"), eq("login-key"), anyString());
security.login("bob", "login-key", null);
As you see, tests passes null
parameter in the second line. And only null
!
I discovered that there was no test that would pass something different from null
there.
Mockito 2.1 will fail with such a sloppy test, because anyString()
matcher doesn’t allow null
anymore.
The newer test that works with Mockito 2.1 is actually more precise:
request.remoteAddress = "127.0.0.2";
doReturn(user).when(loginService).tokenLogin("bob", "login-key", "127.0.0.2");
...
You see it? You don’t need all these obscure eq
and anyString
. Much better!
5) You will discover mystical red tests
You will find some red tests for which it’s really hard to understand why they fail.
For example:
@Test
public void requestByReferenceNumberNeverCreatesSubscription() {
RequestByReferenceNumber requestByReferenceNumber = new RequestByReferenceNumber(user, "12345678901234567890");
when(gisgmpService.request(any(RequestByDrivingLicense.class))).thenReturn(requestByReferenceNumber);
GISGMP.requestCharges("12345678901234567890");
...
It took quite a long time to solve this issue. I could not find out why this test started to fail with Mockito 2.
Please note the second line. Obviously authors wanted to write
any(RequestByReferenceNumber.class)
instead of any(RequestByDrivingLicense.class)
(they both inherit the same superclass).
It seems to be a bug of Mockito 1: it allowed using any(AWrongClass.class)
,
and this incorrect test have been green for several years. :(
6) You will find out that anyList() and anyCollection() are now different things
For example, this code worked with Mockito 1:
@Test
public void domesticPaymentInForeignCurrencyCanBeEnabled() {
doCallRealMethod().when(Payments.accountService).forOperation(anyList(), eq(DOMESTIC));
Collection<Account> accounts = ...
return accountService.forOperation(accounts, DOMESTIC);
Note that mock in the first line uses anyList()
, but the code
actually passes variable accounts
of type Collection
(thought it’s actually List).
Mockito 2 doesn’t allow it anymore. You need to mock more precisely using anyCollection()
.
And you will be happy
To summarize: you will see a lot of pain, but you will become happy. The tests got better, the world got lighter.
Peace 2.1.0!