PowerMockito final method invoke verification not working

Im testing a Task class that runs until an AtomicBoolean value changes. I'm using PowerMockito because get() method of AtomicBoolean instance is final.

MyTaskTest looks like this:

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.concurrent.atomic.AtomicBoolean;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(AtomicBoolean.class)
public class MyTaskTest {

    private AtomicBoolean stopReceiving;
    private MyTask task;

    @Before
    public void setUp() {
        stopReceiving = PowerMockito.mock(AtomicBoolean.class);
        task = new MyTask(stopReceiving);
    }

    @Test
    public void test_run_when_StopReceivingIsFalseFromTheBeginning_should_invokeGetOnlyOnce() {
        // given
        PowerMockito.when(stopReceiving.get()).thenReturn(false);
        // when
        task.run();
        // then
        verify(stopReceiving, times(1)).get();
    }

}

And MyTask class looks like this:

import java.util.concurrent.atomic.AtomicBoolean;

public class MyTask implements Runnable {

    private final AtomicBoolean stopReceiving;

    MyTask(AtomicBoolean stopReceiving) {
        if (stopReceiving == null) {
            throw new NullPointerException("stopReceiving is null. This argument should not be null");
        }

        this.stopReceiving = stopReceiving;
    }

    @Override
    public void run() {
        while (stopReceiving.get() == true) {
            System.out.println("I should not get to here!");
        }
    }
}

The problem here is that check verify(stopReceiving, times(1)).get(); fails with:

Wanted but not invoked java.util.concurrent.atomic.AtomicBoolean.get(); Actually, there were zero interactions with this mock.

which does not make sense to me because it is clearly called in run() method. I even debugged it and it passed through run method, but somehow did not increment invoke counter on it. What am I doing wrong here?

P.S. I'm using PowerMockito version 1.6.2

What I tried

Upvotes: 1

Views: 1838

Answers (2)

Jeff Bowman
Jeff Bowman

Reputation: 95704

You're running into this trouble because you're trying to mock a system class. As specified in the PowerMock documentation, PowerMock can't prepare system classes for test, so you need to prepare the class that calls the system class instead as you have in your answer.

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyTask.class)
public class MyTaskTest {

Under the hood: Normal Mockito works by dynamically creating a subclass of the class you're mocking, and returning that instead; the delegation to Mockito happens through normal polymorphism. With final methods, Java doesn't need to do that virtual method lookup for dynamic dispatch; Java knows which implementation to call at compile time, and calls the implementation via static dispatch before Mockito can intervene.

PowerMock works around this by rewriting (bytecode-manipulating) the class that contains the final method, so that the implementation changes to delegate to EasyMock or Mockito stubs and mocks; in order for this to take effect, though, PowerMock has to install the modified class (AtomicBoolean here) early enough in the classpath that it overrides any other implementation it can find.

However, you can't ever install a replacement higher in priority than the system classes, because they're already loaded in the root classloader; nothing will pre-empt them. PowerMock works around this by rewriting your system-under-test, replacing the call to AtomicBoolean.get() with a call to a replacement method that PowerMock can modify. Therefore, it is not the case that "PowerMockito is somehow treating final methods as special", it is the case that javac treats them as special (with a static-dispatch call) and PowerMock treats system classes as special (because it can't override them the usual way).


Related reading: Lukáš Krejčí's "The Dark Powers of PowerMock"

Side notes:

  • For your use-case in particular, you probably don't need a mock AtomicBoolean at all; use a real one on a different thread, test that the thread is alive, set the boolean, and test that the thread is dead. You may need to poll over time, the way Mockito does for its timeout verification mode.
  • Don't forget that stopReceiving.get() == true can always be replaced with stopReceiving.get().

Upvotes: 2

Adding MyTask.class to @PrepareForTest annotation fixed the problem.

...
@RunWith(PowerMockRunner.class)
@PrepareForTest({AtomicBoolean.class, MyTask.class})
public class MyTaskTest {
...

I couldn't find any info on why is that needed. Only explanation is that PowerMockito is somehow treating final methods as special, so in order for verify to work, you need to add a class that is using the mock with final method call mocked.

Upvotes: 0

Related Questions