First of all it might sound a bit strange – test private method. Why shall I do it – its private, not exposed and tend to change or disappear at all during some code refactoring. Although we might need to mock private method to dissociate from whatever it does, all the complexity, just use an output it supposed to produce. Both approaches are questionable and mean that something wrong is with code design. But anyway say we need it. Especially second option might be sometimes attractive when trying to write a test for some kind of legacy code and a method we’re mocking returns some context related hardly externally simulated data.
So here I’ll try to approach following options:
- Test private method
- Mock private method
- Mock new object creation
For the sample purposes I’ll use this dummy class as a test subject:
public class TestSubjectClass { public String doSomething() { Integer number = new Integer(1); return internalLogic(number.toString()); } private String internalLogic(String someValue) { return "Here is an input: " + someValue; } }
In this class our test points will be mocking new Integer creation, testing directly method internalLogic and mocking it.
1. Testing private method.
@Test public void testPrivateMethod() throws Exception { TestSubjectClass testedClass = new TestSubjectClass(); String actual = Whitebox.invokeMethod(testedClass, "internalLogic", "Test method input value"); assertThat(actual, CoreMatchers.containsString("Test method input value")); }
Whitebox here as per definition from javadoc stands for: “Various utilities for accessing internals of a class. Basically a simplified reflection utility intended for tests.”
2. Mocking private method.
@Test public void mockedPrivateMethod() throws Exception { TestSubjectClass testedClassSpy = spy(new TestSubjectClass()); when(testedClassSpy, method(TestSubjectClass.class, "internalLogic")).withArguments(anyString()).thenReturn("TestInternalLogicOutcome"); String output = testedClassSpy.doSomething(); PowerMockito.verifyPrivate(testedClassSpy, times(1)).invoke("internalLogic", "1"); assertThat(output, CoreMatchers.containsString("TestInternalLogicOutcome")); }
spy method used here when creating test subject, enables us to execute tested code leveraging reflection capabilities and hook on desired internal execution points.
3. Mocking new object creation.
@Test public void mockNewObjectCreation() throws Exception { TestSubjectClass testedClass = new TestSubjectClass(); Integer mockedValue = new Integer(5); PowerMockito.whenNew(Integer.class) .withAnyArguments().thenReturn(mockedValue); String output = testedClass.doSomething(); assertThat(output, CoreMatchers.containsString("Here is an input: 5")); }
When using Powermock we need to add couple extra annotations for test class. Need to use PowerMock test runner to leverage reflection capabilities to hit the goal of testing private stuff.
@RunWith(PowerMockRunner.class) @PrepareForTest(TestSubjectClass.class) public class TestSubjectClassTest { //... }
In order to avoid issues in running test with Powermock, its important to use compatible Mockito and PowerMock versions. You can check compatible versions here. In the examples above I’ve been using following dependencies:
<dependencies> <!-- //mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- //mvnrepository.com/artifact/org.powermock/powermock-module-junit4 --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency> <!-- //mvnrepository.com/artifact/org.powermock/powermock-api-mockito --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency>
Tiny Maven project with the sample above can be downloaded here.
Thats it. Enjoy testing!