2

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 .

seeker
  • 6,841
  • 24
  • 64
  • 100
  • Your solution to mock `ClassToTest.INSTANCE` seams to work, if you add `testClass.doSomething(); verify(testClass).doSomething();` to the `dummy` method, then the tests passes, so I don't see what actually the problem is (I used attempt B). Could you add some more information regarding what the issue is? – Gergely Toth Sep 10 '16 at 18:30
  • @GergelyToth - I'm guessing this may have to do with the call to `AbstractSuperClass` from the private constructor, I have edited the question and added more details of the class. – seeker Sep 10 '16 at 19:08
  • I'm curious to see if there are any solutions to this problem using Mockito/PowerMock. In my case I was looking to test another class that had `ClassToTest` as a dependency, so I basically extracted `ClassToTest` to an interface and provided a mock implementation for the purposes of test, similar to whats explained [here](http://stackoverflow.com/a/2302208/919858) – seeker Sep 10 '16 at 21:35

3 Answers3

2

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)

1

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());
    }
}
Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122
  • This fails at `PowerMockito.mock(ClassToTest.class)` again caused by line 1 and line 2 respectively, which implies the call hasn't been mocked out :( . – seeker Sep 10 '16 at 18:34
  • I copied/pasted your code without AbstractSuperClass as you did not provide it and it works – Nicolas Filotto Sep 10 '16 at 18:36
  • please provide AbstractSuperClass then – Nicolas Filotto Sep 10 '16 at 18:44
  • @NicolasFilotto, I think that may be the difference in us seeing different results(?) The `AbstractSuperClass` actually loads a bunch of configurations in my case and `super()` might be causing the failure. – seeker Sep 10 '16 at 18:45
  • it's hard to help if you don't provide the missing class following the MCVE rule http://stackoverflow.com/help/mcve – Nicolas Filotto Sep 10 '16 at 18:57
  • @NicolasFilotto, sorry for the delay, (was pulled into some other stuff) Ive actually added a snippet from the `AbstractSuperClass`.. I hope that helps us progress. – seeker Sep 10 '16 at 19:09
1

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!

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 2
    This. Powermock is a cruel mistress - it will help you in the short term but hurt you in the long term. As mentioned, it pacifies bad design as well as consumes your `@RunWith` and can cause tricky compatibility issues with other libraries because of what it does. – markdsievers Sep 11 '16 at 09:24
  • 1
    As usual, everything in life, and also in IT life is about balancing. If you are dealing with 3rd party/legacy code; that you can or want not change; then Powermock might still be helpful. But the very simple rule is: if you are creating new code, then you do not **need** Powermock. End of story. – GhostCat Sep 11 '16 at 12:29
  • This is not an answer to the question at hand, but your personal opinion on how code should be done. – bogdan.mustiata Apr 12 '23 at 12:49