Introduction
This document presents two Maven example projects for mocking final and static methods using PowerMockitofor Java unit testing. One project is for JUnit, the other project is for TestNG.
Background
When writing a unit test, we may constantly need to mock certain classes, so we do not need to go through all the full running mechanism to test the code. According to the stack-overflow discussion, Mockito is a highly regarded mocking framework. The problem though is that Mockito by itself does not have the ability to mock final and static methods. If we want to mock these methods, we will need to use PowerMock withPowerMockito. Unfortunately the documentation on how to use PowerMockito is not very detailed. I had to go through quite some try-and-error to make it to work.
- Most of the examples to use PowerMock skip the package "import" section in the Java code. I felt that I had difficulty to figure out which package the classes used in the code belong to;
- There are two commonly used unit test environments in Java, JUnit and TestNG. In the different unit test environments, we need to use PowerMock differently.
The examples in this document will keep a record for me and possibly save some time for the people who are also interested in this subject. PowerMock can be used with either EasyMock or Mockito. This document only shows the usage of PowerMockito.
Install Maven
The attached examples are Maven projects. We need to have Maven to run them. If you do not have Maven on your computer, you will need to install it. Maven is a Java application, you will need to have a JDK and a JRE is not sufficient. The Java version used in my testing is "jdk1.7.0_60". After installing the JDK, you can go the Mavenwebsite to download Maven. The Maven version used in my testing is "3.2.1". According to the documentation, we will need to go through the following steps to complete the Maven installation on a Windows computer.
- Unzip the downloaded zip file to a directory where you want to install Maven;
- Add M2_HOME to the environment variables and set it to the directory where the Maven files are located. In my case, it is C:\Maven;
- Add the M2 environment variable with the value %M2_HOME%\bin;
- Append %M2% to the Path environment variable;
- Make sure that JAVA_HOME exists in the environment variables and it is set to the location of the desired JDK. In my case, it is C:\Program Files\Java\jdk1.7.0_60;
- Make sure that %JAVA_HOME%\bin is in the Path environment variable.
In the Windows environment, many people may be confused about the difference between the user environment variables and the system environment variables. The differences are the following,
- The system variables are available to all the users, but the user variables are only available to the user who is currently logged in to the computer;
- If the same environment variable (except the Path environment variable) is defined in both the user variable and the system variable sections, the value in the user section overrides the value in the system section;
- If the Path environment variable is defined in both the user variable and the system variable sections, the effective Path will be the result of appending them together.
After completing all the steps, we can open the Command Prompt Window and type in the following Maven command,
mvn --version
We should see the following result, indicating that the Maven installation should have been successful.
The Final and Static Classes to Mock
In order to demonstrate PowerMockito's ability to mock final and static methods, I created the following simple classes.
package org.song.example;
public final class AFinalClass {
public final String echoString(String s) {
return s;
}
}
package org.song.example;
public class AStaticClass {
public static final String echoString(String s) {
return s;
}
}
Each class implements a method called "echoString()". I will be using these two methods to demonstrate how to mock them in the unit test programs using PowerMockito.
The Maven Dependencies
This document comes with two example Maven projects. One is for JUnit and the other is for TestNG. If we want to run the unit tests with JUnit, we will need to declare the following dependencies:
<dependency>
<groupId>junit</groupId>
If we want to run the unit tests with TestNG, we will need to declare the following dependencies:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>1.5.5</version>
<exclusions>
<exclusion>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
</exclusion>
<exclusion>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.powermock</groupId>
<artifactId>powermock-reflect</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
We may need to pay some attention on the following issues on the Maven dependencies:
- According to the Maven documentation, the TestNG package declared JUnit as its dependency, but it is declared as an "optional" dependency. Maven will not automatically add optional dependencies to its classpth. This is the case for this TestNG example, where JUnit is not needed;
- In many cases, if the Maven packages are well defined, Maven can handle the transitive dependenciesnicely. But it is always better to make sure exactly what packages are added to the Maven classpath. This is the reason why package exclusions are used in the dependency declaration of the "powermock-module-junit4" and "powermock-module-testng" packages to avoid potential version inconsistencies and confusions.
The Test Classes
If we want to use PowerMockito with JUnit, we will need to write the unit test classes like the following:
package org.song.example;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({AFinalClass.class, AStaticClass.class})
public class MockTest {
@Test
public void mockFinalClassTest() {
AFinalClass tested = PowerMockito.mock(AFinalClass.class);
final String testInput = "A test input";
final String mockedResult = "Mocked final echo result - " + testInput;
Mockito.when(tested.echoString(testInput)).thenReturn(mockedResult);
// Assert the mocked result is returned from method call
Assert.assertEquals(tested.echoString(testInput), mockedResult);
}
@Test
public void mockStaticClassTest() {
PowerMockito.mockStatic(AStaticClass.class);
final String testInput = "A test input";
final String mockedResult = "Mocked static echo result - " + testInput;
Mockito.when(AStaticClass.echoString(testInput)).thenReturn(mockedResult);
// Assert the mocked result is returned from method call
Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
}
}
- Add annotation "@RunWith(PowerMockRunner.class)" to the test class;
- Add annotation "@PrepareForTest({AFinalClass.class, AStaticClass.class})" to the test class, where the "AFinalClass" and "AStaticClass" are the classes being tested.
If we want to run the unit tests with TestNG, we will need to write the test classes like the following:
package org.song.example;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
@PrepareForTest({AFinalClass.class, AStaticClass.class})
public class MockTest extends PowerMockTestCase {
@Test
public void mockFinalClassTest() {
AFinalClass tested = PowerMockito.mock(AFinalClass.class);
final String testInput = "A test input";
final String mockedResult = "Mocked final echo result - " + testInput;
Mockito.when(tested.echoString(testInput)).thenReturn(mockedResult);
// Assert the mocked result is returned from method call
Assert.assertEquals(tested.echoString(testInput), mockedResult);
}
@Test
public void mockStaticClassTest() {
PowerMockito.mockStatic(AStaticClass.class);
final String testInput = "A test input";
final String mockedResult = "Mocked static echo result - " + testInput;
Mockito.when(AStaticClass.echoString(testInput)).thenReturn(mockedResult);
// Assert the mocked result is returned from method call
Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
}
}
- Add annotation "@PrepareForTest({AFinalClass.class, AStaticClass.class})" to the test class, where the "AFinalClass" and "AStaticClass" are the classes being tested;
- The test class needs to extend the "PowerMockTestCase" class. According to the PowerMockito documentations, extending the "PowerMockTestCase" class is just one of the options to make the test class to work, but it also mentioned that extending the "PowerMockTestCase" class is the "safe" option.
Run the Unit Tests
If you want to run the example projects, you can download the attached zip files and unzip them to your desired folder. The attached projects are simple standard Maven projects. After unzipping them, you will see the standard "src" folder and the "pom.xml" file. You can open the Command Prompt Window and go to the folder that has the "pom.xml" file. You can then issue the following command:
mvn clean test
The following picture shows that the mocking of the final and static methods is successful in the JUnit testing environment.
If a project declares only JUnit or TestNG dependency but not both, Maven will use the declared unit test environment to run the tests. This is the case for the attached simple examples. In more complex projects, if both test environments are declared, you will need to make sure the desired unit test environment is used.
Additional Notes
PowerMockito and Mockito Work Together
In the test programs, it is not uncommon that some test cases have final or static methods to mock, while the others do not. It is important that we do not extend the "PowerMockTestCase" class if the test cases do not have final or static methods to mock. TestNG will use different object factory to create the test case instances in the two cases. If we extend the "PowerMockTestCase" class when there is no final nor static methods to work with, the unit tests will not run consistently under Surefire in Maven.
The Scope of the Final and Static Mocks
It is very common that in the same test class, we have more than one test methods. Let us take a look at the following test class.
package org.song.example;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
@PrepareForTest({AFinalClass.class, AStaticClass.class})
public class MockTest extends PowerMockTestCase {
private AFinalClass aFinalClass_mock = null;
@Test
public void mockFinalClassTest() {
final String testInput = "A test input";
final String mockedResult = "Mocked final echo result - " + testInput;
aFinalClass_mock = PowerMockito.mock(AFinalClass.class);
Mockito.when(aFinalClass_mock.echoString(testInput)).thenReturn(mockedResult);
// Assert the mocked result is returned from method call
Assert.assertEquals(aFinalClass_mock.echoString(testInput), mockedResult);
}
@Test(dependsOnMethods = {"mockFinalClassTest"})
public void mockFinalClassTest_1() {
final String testInput = "A test input";
final String mockedResult = "Mocked final echo result - " + testInput;
// Assert the mocked result is returned from method call
Assert.assertEquals(aFinalClass_mock.echoString(testInput), mockedResult);
}
@Test
public void mockStaticClassTest() {
PowerMockito.mockStatic(AStaticClass.class);
final String testInput = "A test input";
final String mockedResult = "Mocked static echo result - " + testInput;
Mockito.when(AStaticClass.echoString(testInput)).thenReturn(mockedResult);
// Assert the mocked result is returned from method call
Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
}
@Test(dependsOnMethods = {"mockStaticClassTest"})
public void mockStaticClassTest_1() {
final String testInput = "A test input";
final String mockedResult = "Mocked static echo result - " + testInput;
// Assert the mocked result is returned from method call
Assert.assertEquals(AStaticClass.echoString(testInput), mockedResult);
}
}
In the above class the "dependsOnMethods" property of the "@Test" annotation tells the test framework to run "mockFinalClassTest()" before "mockFinalClassTest_1()" and "mockStaticClassTest()" before "mockStaticClassTest_1()". If we run the test, we will get the following.
- Both the "mockFinalClassTest_1()" and "ockStaticClassTest_1()" methods failed on the assertion;
- This is because the scope of the mocks is limited to the test method where they are specified.
The Scope of Regular Mockito Mocks
If a method is neither final nor static, we can simply use Mockito to mock it. The scope of the mock is different from the mocks for final and static methods. Let us take a look at the following example.
package org.song.example;
public class RegularClass {
public String Echo(String s) {
return s;
}
}
The "RegularClass" is the class to be tested, and the following is the unit test class.
package org.song.example;
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.Test;
public class RegularTest {
private RegularClass instance = null;
@Test
public void test1() {
final String expected = "Expected String";
instance = Mockito.mock(RegularClass.class);
Mockito.doReturn(expected).when(instance).Echo(Mockito.anyString());
Assert.assertEquals(instance.Echo("Song"), expected);
}
@Test(dependsOnMethods = {"test1"})
public void test2() {
final String expected = "Expected String";
Assert.assertEquals(instance.Echo("Song"), expected);
}
}
- The "test1" method initiated a Mockito mock for the "RegularClass" and assigned it to the instance variable "instance";
- The "test2" simply uses the "instance" for the testing without re-initiating the mock.
If we run the unit test, we can see that both test methods run successfully.
This experiment shows us that the scope of the mocks created by regular Mockito goes beyond the limit of the test method where the mock is created, which is different from the scope of the mocks on final and static method created by PowerMockito.
Mock Object Construction (new) with PowerMockito
One of the challenges of the unit testing is to mock the locally created objects. There are many discussions on how to make the code more unit-testable by applying some desired design patterns and if we should usedependency injections. Despite these good design patterns, PowerMockito does have the ability to mock locally created objects. Let's take a look at the following two classes.
package org.song.example;
import java.util.Random;
public class ScoreGrader {
public int getScore() {
Random random = new Random();
int score = 60 + (int) Math.round(40.0 * random.nextDouble());
return score;
}
}
package org.song.example;
public class Student {
public int getMathScore() {
ScoreGrader grader = new ScoreGrader();
return grader.getScore();
}
}
- The "ScoreGrader" class implemented a method "getScore" that returns a randomly generated integer between 60 - 100;
- The "getMathScore" method in the "Student" class instantiated a "ScoreGrader" object and used it to generate the math score for the student.
The following is the unit test for the "Student" class that mocked the object construction (new) of the "ScoreGrader" class.
package org.song.example;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
@PrepareForTest({Student.class})
public class MockConstructionTest extends PowerMockTestCase {
@Test
public void mockConstruction() {
Student student = new Student();
final int expectedScore = 120;
ScoreGrader grader_mock = Mockito.mock(ScoreGrader.class);
Mockito.doReturn(expectedScore).when(grader_mock).getScore();
try {
PowerMockito.whenNew(ScoreGrader.class)
.withNoArguments().thenReturn(grader_mock);
} catch (Exception e) {
Assert.fail("Unable to mock the construction of "
+ "the ScoreGrader object");
}
// This assertion will succeed because the mock is used to
// generate the score, so a score greater than 100 is generated
Assert.assertEquals(student.getMathScore(), expectedScore);
}
}
- In the "@PrepareForTest" annotation, we need to specify the class where the "new" object construction happens, not the class being constructed;
- The call to the method "PowerMockito.whenNew()" can alter the object construction, so the construction process can return an object mock to the caller.
If we run the test, we will find that it succeeds. This indicates that the mock is obtained when the "ScoreGrader grader = new ScoreGrader();" statement is issued, because a true "ScoreGrader" object can never generate a score larger than 100.
Mock Singleton
package org.song.example;
import java.util.Random;
public class SingletonScoreGrader {
private static SingletonScoreGrader instance = null;
private SingletonScoreGrader() {}
public static synchronized SingletonScoreGrader instance() {
if (instance == null) {
instance = new SingletonScoreGrader();
}
return instance;
}
public int getScore() {
Random random = new Random();
int score = 60 + (int) Math.round(40.0 * random.nextDouble());
return score;
}
}
- The "SingletonScoreGrader" class is a typical singleton class;
- The "instance" method returns the single instance of the "SingletonScoreGrader" object.
package org.song.example;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
@PrepareForTest({SingletonScoreGrader.class})
public class MockSingletonTest extends PowerMockTestCase {
@Test
public void mockSingleton() {
final int expectedScore = 120;
PowerMockito.mockStatic(SingletonScoreGrader.class);
SingletonScoreGrader singletonMock = Mockito.mock(SingletonScoreGrader.class);
Mockito.doReturn(expectedScore).when(singletonMock).getScore();
Mockito.when(SingletonScoreGrader.instance()).thenReturn(singletonMock);
Assert.assertEquals(SingletonScoreGrader.instance().getScore(), expectedScore);
}
}
To mock the singleton class we can simply create a mock of the class and mock the static "instance" method to return the mock. Running the above test, we will find it finishes successfully.
Besides "Mockito.doReturn()", let's "Mockito.doAnswer()"
When using Mockito, we can use "Mockito.doReturn()" to mock the behavior of a method and alter the object returned by the method. While it is very easy to use, the "Mockito.doAnswer()" method is much more powerful.
package org.song.example;
import java.util.ArrayList;
import java.util.List;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
@PrepareForTest({AFinalClass.class})
public class DoAnswerTest extends PowerMockTestCase {
@Test
public void doAnswerTest() {
final String testInput = "A test input";
final String mockedResult = "Mocked final echo result - " + testInput;
AFinalClass aFinalClass_mock = PowerMockito.mock(AFinalClass.class);
final List<String> answers = new ArrayList<String>();
final String mockIsUsed = "The mock is used to generate the result";
Mockito.doAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
answers.add(mockIsUsed);
return mockedResult;
}
}).when(aFinalClass_mock).echoString(testInput);
// Assert the mocked result is returned from method call
Assert.assertEquals(aFinalClass_mock.echoString(testInput), mockedResult);
// In addition to the mocked result, we can keep additional information of
// the calling of the mock and add complex logic in the mock
Assert.assertEquals(answers.size(), 1);
Assert.assertEquals(answers.get(0), mockIsUsed);
}
}
- The "Mockito.doAnswer()" method has the same capability as "Mockito.doReturn()" to specify the expected mocked result returned by a method;
- It is more powerful because it allows us to define a true method to control the generation of the mocked result and keep the calling history of the mocked method.
Running the above test, we can see the following result.
Test Listeners with Surefire
This topic is not directly related to Mockito, but it may be helpful when doing the unit tests. It is not uncommon that we may want to perform some actions, such as initializing a test database, before any unit test case to start. If these actions fail, we want to fail and quit the unit test with failure. This can be easily done with Surefire listeners. We can configure the listeners in the POM like the following:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
<configuration>
<properties>
<property>
<name>listener</name>
<value>org.song.example.MyRunListener</value>
</property>
</properties>
<systemPropertyVariables>
<P1>P1 value</P1>
<P2>P2 value</P2>
</systemPropertyVariables>
</configuration>
</plugin>
Besides adding a listener, we also added some "systemPropertyVariables" to the Surefire configuration. The "MyRunListener" is implemented as the following:
package org.song.example;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
public class MyRunListener implements ITestListener {
public void onTestStart(ITestResult result) {}
public void onTestSuccess(ITestResult result) {}
public void onTestFailure(ITestResult result) {}
public void onTestSkipped(ITestResult result) {}
public void onTestFailedButWithinSuccessPercentage(ITestResult result) {}
public void onStart(ITestContext context) {
System.out.println("Test Started globally - "
+ System.getProperty("P1") + " - " + System.getProperty("P2"));
Assert.fail("Artificially failed the test");
}
public void onFinish(ITestContext context) {}
}
The "MyRunListener" implements the "ITestListener" interface, which defines many events for the listener to listen to, but the "MyRunListener" only takes action on the "onStart" event for simplicity. After running the unit test, we can see the following result.
- The values configured in the "systemPropertyVariables" section are printed out by the code;
- The unit test failed because we asserted an artificial failure. As expected, this failure also quit the unit test before any test case to start.
Suppressing Unwanted Behavior and WhiteBox
Instead of making my own examples, let me simply add the links to the references here. When mocking some objects, we may find the following links are useful and sometimes critical.
- https://code.google.com/p/powermock/wiki/SuppressUnwantedBehavior;
- https://code.google.com/p/powermock/wiki/BypassEncapsulation.
Making Test Classes Available to Other Modules
It is not very common, but sometimes, you may want to make the test classes in one module available to other modules. By default, Maven does not add test classes in the package, but you can add the follow section in the POM file.
<plugins>
<!-- .... other plugins -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals><goal>test-jar</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
After a successful build of the module, we should have two jar files in the "target" folder. One of the jar file is the regular jar file for the functional classes in the module, the other one will package the test classes. To use the test jar file in the other modules, we can add the following dependency.
<dependency>
<groupId>the-group-id</groupId>
<artifactId>the-artifact-id-of-the-module</artifactId>
<version>the-version</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
The dependency scope is "test", so the test classes will only be available to the test classes in the depending module, and it is not transitive.
Proper Way to Extend "PowerMockTestCase"
According to the PowerMock documentation, the "safe" way to tell TestNG to use PowerMock is to let the test classes to extend the "PowerMockTestCase" class from the "org.powermock.modules.testng" package. It work fine but there is a trick. What I have learned from my experience is that we should never let an abstract class to extend the "PowerMockTestCase". If we do it, the TestNG may fail, and the Surefire will not tell us what exactly going on and way.
Points of Interest
- This document presented two running Maven example projects for mocking final and static methods using PowerMockito in Java unit testing;
- PowerMock can be used with either EasyMock or Mockito. This document only shows the usage ofPowerMockito;
- There are some discussions to avoid final and static methods in the Java code to make it more unit testable. But sometimes, we may need to mock third-party Java code and we do not have the options to bypass the final and static methods, plus the final and static methods do have their indispensable values in the Object-oriented principles;
- I hope you like my postings and I hope this article can help you one way or the other.
No comments:
Post a Comment