A common problem for Java developers that wish to get comprehensive unit test coverage is handling the mocking of singletons that are implemented using static method calls.
Let’s look at how we can mock singleton behavior using three common Java testing libraries Mockito, EasyMock and JMockit.
Part 1: Write Code to Test
To start with, we need a simple contrived example. In this example, we have a class ClassUnderTest that accesses a singleton Singleton and then, based on the return value, calls a method in DummyApiClient.
The issue is, we need to be able to mock the behavior of the static instance of Singleton in order to test ClassUnderTest.
public class ClassUnderTest {
private DummyApiClient dummyApiClient;
public ClassUnderTest(DummyApiClient dummyApiClient){
this.dummyApiClient = dummyApiClient;
}
public void methodUnderTest(){
if(Singleton.getSingleton().getBool()){
dummyApiClient.doPositive();
} else {
dummyApiClient.doNegative();
}
}
}
import java.util.Random;
public class Singleton {
static Singleton singleton;
static {
singleton = new Singleton();
}
public static Singleton getSingleton(){
return singleton;
}
public Boolean getBool(){
return new Random().nextBoolean();
}
}
public class DummyApiClient {
public void doPositive(){ System.out.println("positive");};
public void doNegative(){ System.out.println("negative");};
}
Part 2: Mockito (mockito-inline) 3.4+
Wow! It’s now possible to mock static methods with mockito, without the additional dependency of PowerMock! Since version 3.4 of Mockito (PR), we can mock static methods using the mockStatic command. (examples)
First, let’s add the required dependencies to our pom.xml file. We need to use JUnit and mockito-inline (regular mockito-core will not work).
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SingletonMockDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.surefire.version>2.22.2</maven.surefire.version>
<junit.version>5.6.2</junit.version>
<mockito.version>3.4.0</mockito.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Next, let’s write our test class, testing the behavior of both true and false from our singleton Singleton.
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import static org.mockito.Mockito.*;
public class ClassUnderTestTest {
DummyApiClient dummyApiClient;
Singleton singletonMock;
ClassUnderTest unit;
@BeforeEach
public void beforeEach(){
dummyApiClient = mock(DummyApiClient.class);
singletonMock = mock(Singleton.class);
unit = new ClassUnderTest(dummyApiClient);
}
@Test
public void testMethodUnderTestPositive(){
when(singletonMock.getBool()).thenReturn(true);
try (MockedStatic<Singleton> staticSingleton = mockStatic(Singleton.class)) {
staticSingleton.when(Singleton::getSingleton).thenReturn(singletonMock);
unit.methodUnderTest();
staticSingleton.verify(Singleton::getSingleton);
verify(singletonMock).getBool();
verify(dummyApiClient).doPositive();
}
}
@Test
public void testMethodUnderTestNegative(){
when(singletonMock.getBool()).thenReturn(false);
try (MockedStatic<Singleton> staticSingleton = mockStatic(Singleton.class)) {
staticSingleton.when(Singleton::getSingleton).thenReturn(singletonMock);
unit.methodUnderTest();
staticSingleton.verify(Singleton::getSingleton);
verify(singletonMock).getBool();
verify(dummyApiClient).doNegative();
}
}
}
Part 3: EasyMock
As of the time of writing, EasyMock does not support mocking of static methods without the use of an additional library; In our case, we will use PowerMock to support mocking static methods.
First, let’s update our pom.xml to reflect the libraries we are using:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SingletonMockDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.surefire.version>2.22.2</maven.surefire.version>
<powermock.version>2.0.2</powermock.version>
<junit.version>4.13</junit.version>
<easymock.version>4.2</easymock.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>${easymock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-easymock</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Next, let’s write out test class ClassUnderTestTest.java
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.easymock.EasyMock.expect;
import static org.powermock.api.easymock.PowerMock.mockStatic;
@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class ClassUnderTestTest {
DummyApiClient dummyApiClient;
Singleton singletonMock;
ClassUnderTest unit;
@Before
public void beforeEach(){
dummyApiClient = PowerMock.createMock(DummyApiClient.class);
singletonMock = PowerMock.createMock(Singleton.class);
mockStatic(Singleton.class);
expect(Singleton.getSingleton()).andReturn(singletonMock);
unit = new ClassUnderTest(dummyApiClient);
}
@Test
public void testMethodUnderTestPositive(){
expect(singletonMock.getBool()).andReturn(true);
dummyApiClient.doPositive();
PowerMock.expectLastCall();
PowerMock.replayAll();
unit.methodUnderTest();
}
@Test
public void testMethodUnderTestNegative(){
expect(singletonMock.getBool()).andReturn(false);
dummyApiClient.doNegative();
PowerMock.expectLastCall();
PowerMock.replayAll();
unit.methodUnderTest();
}
}
Resetting Mocks: Note how mockStatic(Singleton.class); doesn’t have a corresponding reset call. This is because PowerMock resets mocks using the @PrepareForTest annoation. (stack overflow post)
Part 4: JMockit
Again, we must first update our pom.xml file to pull in our required dependencies. You need to update the surefire configuration to use the javaagent initilization parameter.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SingletonMockDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.surefire.version>2.22.2</maven.surefire.version>
<junit.version>5.6.2</junit.version>
<jmockit.version>1.49</jmockit.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
<configuration>
<argLine>
-javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
</argLine>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
And, then we need to write our test class, ClassUnderTestTest, this time using JMockit.
import mockit.Expectations;
import mockit.Mocked;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ClassUnderTestTest {
@Mocked
DummyApiClient dummyApiClient;
@Mocked
Singleton singletonMock;
ClassUnderTest unit;
@BeforeEach
public void beforeEach(){
unit = new ClassUnderTest(dummyApiClient);
}
@Test
public void testMethodUnderTestPositive(){
new Expectations(){{
singletonMock.getBool(); result = true;
Singleton.getSingleton(); result = singletonMock;
dummyApiClient.doPositive();
}};
unit.methodUnderTest();
}
@Test
public void testMethodUnderTestNegative(){
new Expectations(){{
singletonMock.getBool(); result = false;
Singleton.getSingleton(); result = singletonMock;
dummyApiClient.doNegative();
}};
unit.methodUnderTest();
}
}