JUnit and Mockito : Best Practices, Do’s and Don’ts

Pre-Requisite : Basic knowledge of JUnit, Mockito and unit testing approach.

Ketan Nabera
4 min readJul 20, 2020

As a software developer, writing (unit) tests has always been as important as writing the application code in the first place. A well-written unit test has often saved me from a lot of hidden/unknown scenarios and has been a major contributor in achieving good code quality as well.
As important it is to write unit tests, it’s equally important for the test to be as precise and accurate as it can be. The more generic a unit test looks, the lesser is its accuracy.

Generally, writing a unit test somewhat looks like :

  • Mocking a few classes and their behavior that are external to the unit that is being tested.
  • Calling the unit that is being tested
  • Asserting data that is returned from the unit, verifying calls to mocked behaviors and also testing exceptions(if any expected).

In all these steps, there are a few good practices which we should try and implement to add more value to the unit test. Let’s have a look :

Using correct VerificationMode for mocks :

While verifying the calls made to mock behaviors, it is always better to test it against the specific behavior that we expect rather then predicting it. For example,

Mockito.verify( myMock , atLeastOnce()).myMockBehavior()
vs
Mockito.verify( myMock , times(n)).myMockBehavior().

As it is pretty clear from the method names, atLeastOnce will test that myMockBehavior is getting executed once in the test flow. But, if we expect test to call this behavior n times and it only does it n-1 times, such that n-1>1 then we would never know as the test would never fail.
Thus, atLeast(),atMost(),atLeastOnce(),atMostOnce() and all such verification modes should be avoided as much as possible.

Using correct Matcher for method calls:

From the above example , If ‘myMockBehvior’ expects some parameters, we should try to assert the parameters also against their expected state, rather then using anyMatchers(provided by Mockito).
E.g, A test for a method that saves a user into database might look like:

Test code:
MyDataBaseService dbService = mock(MyDataBaseService.class);
User user =new User(‘abc@def.com’);
myTestUnit.save(user);

Actual Code (MyTestUnit.java):
void save(User user){ dbService.save(user)}

Now, at this point, there are two options to verify that dbService is called :
1. Mockito.verify(dbService,times(1)).save(any(User.class))
vs
2. Mockito.verify(dbService,times(1)).save(eq(user))

Needless to say, the second approach is more accurate. The first option would only check that the parameter passed to save is of type User.
The second option would actually test the object equality. So,if tomorrow someone changes the code in save() method and modifies the state of the user before dbService.save() is called, the test would fail. This would mean its always ensured that we are saving what is expected to get saved and thus increase test accuracy.

Using ArgumentCaptor for verifying object state :

Using ArgumentCaptor , is another good way to test the method arguments.
ArgumentCaptor helps to capture the parameters that are passed to mocked behaviors and we can always write assertions on the values that we expect in those arguments.
E.g. In the same example as above we can also use:

ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class);
User expectedUserForSave = userCaptor.getValue();
// call the method to be tested.
assert(‘abc@def.com’,user.getEmail());

Testing for checked exceptions :

Unit testing is meant to cover all possible scenarios, all possible ways the code might flow under different data conditions that one can think of, and testing for checked exceptions is no exception (pun intended!).
If the code is supposed to throw any checked exception (one that might arise out of data/business scenarios ), and we have written try-catch or used throws with our unit of code. Then, it is absolutely important to check that correct exception is thrown when expected.
JUnit provides out of the box implementations for testing exceptions and we should use it to solidify our tests.
Both JUnit4 and JUnit5, have different ways to test these, both being fairly simple and easy to implement.
Have a read here : https://www.baeldung.com/junit-assert-exception.

Note : It is also important to test for the actual Exception class that is being thrown rather then testing for java.lang.Exception class as that again hides some level of detail from the test.

To summarize it , its always better to cover as many scenarios as one can in the unit tests and also writing as accurate assertions/verify calls as possible. It might not always be possible to use all of these pointers, but it really helps a lot if we can reach there. This way one can always see failure in tests with the smallest of changes and helps reduces bugs due to refactoring.

Like I said, this is just the tip of the mountain, and there are a lot of ways in which we can always improve our tests.
Do let me know in comments, what is your testing strategy. :)

Happy Testing, Happier Coding! ;)

--

--