How do I mock a private static final field initialized via a private constructor using Powermock and Mockito?

12.9k Views Asked by At

Here's my source class -

public class ClassToTest extends AbstractSuperClass<Integer> {
    private static final ClassToTest INSTANCE = new ClassToTest(); // (line 1) need to mock this variable

    static ClassToTest get() {
        return INSTANCE;
    }
    private ClassToTest() {
        super(Integer.class);// (line 2)
    }
}

Here's my attempt so far at testing it

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassToTest.class)
public class TestClass {
    private ClassToTest testClass;
    @Before
    public void setUp() {
        // each of the below attempts fails at line 1  because of the call to line 2 (annotated above).
        // Attempt A.  
        testClass = WhiteBox.newInstance(ClassToTest.class);
        //Attempt B.
        testClass = mock(ClassToTest.class);
        WhiteBox.setInternalState(ClassToTest.class, "INSTANCE", testClass);
    }
    @Test
    public void dummy() {
        // irrelevant
    }
}

I'm trying to effectively mock out ClassToTest.INSTANCE and the call its private constructor. How could I do that?

EDIT : Snippet/Constructor invoked from AbstractSuperClass.

public abstract class AbstractSuperClass<V extends Serializable> {
    private final CacheClient<V> cache;
    private final int seconds;

    public AbstractSuperClass(Class<V> valueType) {
        cache = new ClientFactory(Config.getAppConfig(), StatisticSet.getGlobalStatistics()).newClient(getCacheType(), valueType);
        seconds = Config.getAppConfig().get(getCacheType().getSectionEnum()).getSeconds();
    }

P.S: I'm trying to stay away from dealing with the internals of AbstractSuperClass and had originally hoped to simply mock the call away. I'm also open to any ideas to refactoring ClassToTest to avoid this .

3

There are 3 best solutions below

6
Nicolas Filotto On

I don't believe that mocking the field is the right approach, I don't even believe that it is possible to do it as you cannot override a field only methods can be overridden which is actually how mocks do and work. Indeed mocks are only somehow proxies to which we override the methods.

You should mock ClassToTest.get() instead as next:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassToTest.class)
public class TestClass {
    private ClassToTest testClass;
    @Before
    public void setUp() {
        testClass = PowerMockito.mock(ClassToTest.class);
        PowerMockito.mockStatic(ClassToTest.class);
        Mockito.when(ClassToTest.get()).thenReturn(testClass);
    }

    @Test
    public void dummy() {
        // Here I get the instance of ClassToTest that I mocked in the setUp method
        System.out.println(ClassToTest.get());
    }
}
3
GhostCat On

Your real problem is that you created hard, almost un-testable code by making use of static. And not only that: you also create a bad design. Because you tightly couple your production classes to each other. Once in place, it will be hard to get rid of that static method later on.

So, the other option: instead of trying to "fix" a broken design by using PowerMock, you step back. You learn what "writing testable" code is actually about (for example by watching these videos); and then you use pure interfaces and dependency injection to solve your problem. And you test all of that with EasyMock or Mockito, without the need to Powermock!

0
AudioBubble On

I don't understand what you are trying to achieve, but this is working here:

@PrepareForTest(ClassToTest.class) // required
@RunWith(PowerMockRunner.class)    // required
public class ClassToTestTest {

    private ClassToTest testClass;

    @Before
    public void setUp() throws Exception {
        this.testClass = Mockito.mock(ClassToTest.class);
        final Field instance = ClassToTest.class.getDeclaredField("INSTANCE");
        instance.setAccessible(true);
        instance.set(null, this.testClass);
    }

    @Test
    public void testGet() throws Exception {
        assertSame(this.testClass, ClassToTest.get());
        System.out.println(this.testClass);
    }
}

Output:

Mock for ClassToTest, hashCode: 1083021083

(Tested with powermock-api-mockito version 1.6.2)