What is mocking?
Unit tests must construct the required arguments for the method under test first (the Arrange section). Many times this is straightforward, such as in the previous example which required only constructing an array of integers. However, it is not always possible or easy to construct and perform actions to call the method under test, and this is a case for using mock objects. A mock object simulates the behavior of a real object, and it can be controlled within the unit test.
For example, connecting to a database or interacting with the filesystem are unsafe operations and may have unintended side effects; these are good cases for using mocking. Instead of opening a real file and writing to it, the test can create a mock file, open it, and write to it. This does not affect the real file system and does not have side effects, but it simulates a real file for testing.
How to set up Mockito in Maven and Gradle
Mockito 5 is a commonly used Java mocking framework and is easy to get started with. Add the following to the dependency section for Maven for this Mockito tutorial:
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.13.0</version>
<scope>test</scope>
</dependency>
or Gradle:
testImplementation("org.mockito:mockito-junit-jupiter:5.13.0")
How to mock a class and define method behavior
Consider a class GaussianDistribution
that wraps around the Java Random number generator. The method generate scales and shifts the result of Random.nextGaussian
and returns the result.
package org.example;
import java.util.Random;
public class GaussianDistribution {
private final Random random;
public GaussianDistribution(Random random) {
this.random = random;
}
public double generate(double mean, double standardDeviation) {
return random.nextGaussian()*standardDeviation+mean;
}
}
Because the result is random, using GaussianDistribution
in tests would be problematic. Randomness in tests would cause tests to change run-to-run. Mocking the class Random can help create stable, deterministic, and isolated tests.
To set up a mock for Random, use
Random mockRandom = mock(Random.class);
Then define the behavior of the mocked class using Mockito.when
and Mockito.thenReturn
like this:
when(mockRandom.nextGaussian()).thenReturn(10.0);
This tells the mock object to always return 10.0 when nextGaussian
is called. Mocking provides a Random object that is deterministic to use in the test. Here is a possible test:
package org.example;
import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GaussianDistributionTest {
@Test
void test() {
// Arrange
Random mockRandom = mock(Random.class); // <- Mock the class
when(mockRandom.nextGaussian()).thenReturn(10.0); // <- Define the behavior
GaussianDistribution gaussianDistribution = new
GaussianDistribution(mockRandom); // <- use the mock
// Act
double result = gaussianDistribution.generate(10, 10);
// Assert
assertEquals(110, result); // <- The result is deterministic
}
}
What are the benefits of using a Java mocking framework?
- Mocks can help isolate code for testing. In the example above, the mock removes the randomness of the Random dependency and focuses on testing the scaling and shifting in GaussianDistribution. It isolates the behavior of GaussianDistribution from Random.
- Easy construction of complex or unknown dependencies. Some dependencies are difficult or impossible to construct for a unit test, such as central dependencies that require a complex setup or dependencies from a third party. Mocking allows you to continue writing tests in these cases if you know the behavior required from the dependency.
Mocking best practices
Mocking is defining the behavior of the mocked class twice – once in the actual class and once in the mocking setup. This can be problematic when changing or refactoring classes that are mocked in tests. Both the class and the mocks in every test may have to be updated. For this reason, it is recommended to mock stable interfaces only when necessary. In the example, Random is a stable class, so this is OK.
You might also find yourself needing to mock final or static classes and methods, or mocking constructors or void methods. Advanced mocking features that allow this are not always considered good practice and often require extra configuration of Mockito. If you find yourself wanting to use these Mockito features, it is usually a hint of a bad code design. That said, not all code is within your control, so using these features is not bad in itself.
Mocks are important to know how to use well, which means you have to really understand your code and its intended behavior; ideally, mocks are used only when necessary. However, new test writers sometimes go overboard, which runs the risk of slowing down tests and making them needlessly complicated. Using too many mocks can even affect whether you’re actually testing the right thing – are you testing that something is implemented in a specific way, rather than testing that the desired outcome?
When isn’t mocking appropriate?
Mocks work best when the code being tested isn’t tightly coupled; decoupled code tends to result from Test Driven Development (TDD), which no doubt partially explains why mocking is encouraged in the TDD community. For tightly coupled code (as is often present in legacy code), it might be the case that two or more classes form one unit, which can still be tested with a unit test. If not, think about whether the paired class has a lot of its own logic and whether it matters to test it separately. If not, don’t write a mock for it.
Similarly, don’t mock value objects; there’s simply no reason to because they don’t have their own logic. Also avoid mocking concrete classes, because these trap you in a certain implementation of the class you’re mocking.
In some cases, different types of “test doubles” similar to mocks are more appropriate instead. For a more detailed discussion about the differences between and applications for mocks, stubs, fakes, dummies, and other test doubles, check out Martin Fowler’s classic article on the topic.
Which mocking framework is right for you?
You can write mocks manually, but a few open source mocking frameworks make it a lot faster and easier to maintain your mocks if you write tests frequently.
Many mocking frameworks for Java code work in combination with JUnit and other popular unit testing frameworks, and are great depending on your specific needs. Two of the most widely used are Mockito and PowerMock. Mockito is useful for all but the most complicated cases, for which you can use PowerMock instead. Fortunately these frameworks are compatible with each other, so you can start in Mockito for most mocks and switch to PowerMock for the more complex cases.
In summary, to incorporate the best mocking practices:
- Get your unit testing techniques in shape
- Choose a testing framework
- Choose a mocking framework
But remember, less is more!
This powerful method only needs a light touch, and can help you write effective unit tests and clean, agile code. With the right tools and the careful application of these techniques, you’ll be mocking in style.
Further reading about Mockito
Mockito offers a whole suite of tools to help you write and verify mocks for unit tests. Here is a brief list to help you get started reading the Mockito documentation to accompany this guide:
- Defining behavior: Stubbing
- Specifying generic arguments: Argument matchers
- Assertions on mocked objects: Verifying method calls