Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mockito strict stubs mode does not work correctly when mocking static methods #976

Open
stevegilbert23 opened this issue Mar 1, 2019 · 0 comments

Comments

@stevegilbert23
Copy link
Contributor

When a test class is using strict stubs and mocking static methods, tests that have extra invocations declared that are not used are not detected as such. They should be detected when strict stubs are being used and the test should fail.

This is due to the location (in the class with the line number) being calculated incorrectly. All static mocks end up having the same location. The strict stub detection puts the invocations into a map keyed by the location and the result is a single entry in the map when there should be one for each "when" for the mock.

This test shows the behavior:

package samples.powermockito.junit4.strictstubs;

import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.internal.stubbing.InvocationContainerImpl;
import org.mockito.internal.util.MockUtil;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.Location;
import org.mockito.invocation.MockHandler;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Stubbing;
import org.powermock.api.mockito.invocation.MockHandlerAdaptor;
import org.powermock.api.mockito.invocation.MockitoMethodInvocationControl;
import org.powermock.core.MockRepository;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import static org.powermock.api.mockito.PowerMockito.doNothing;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(value = PowerMockRunner.class)
@PowerMockRunnerDelegate(MockitoJUnitRunner.StrictStubs.class)
@PrepareForTest({StrictStubsTest.SomethingWithStaticMethod.class})
public class StrictStubsTest {

    static class SomethingWithStaticMethod {
        static String doStaticOne() {
            return "I am static 1";
        }

        static String doStaticTwo() {
            return "I am static 2";
        }

        static void doStaticVoid() {
        }
    }

    @Test
    public void shouldFailBecauseInvocationDidNotOccur() throws Exception {
        mockStatic(SomethingWithStaticMethod.class);
        when(SomethingWithStaticMethod.doStaticOne()).thenReturn("Something else 1");
        when(SomethingWithStaticMethod.doStaticTwo()).thenReturn("Something else 2");
        doNothing().when(SomethingWithStaticMethod.class, "doStaticVoid");

        MockitoMethodInvocationControl invocationControl =
                (MockitoMethodInvocationControl)
                        MockRepository.getStaticMethodInvocationControl(SomethingWithStaticMethod.class);
        MockHandlerAdaptor mockHandlerAdaptor = invocationControl.getMockHandlerAdaptor();
        Object mock = mockHandlerAdaptor.getMock();
        MockHandler<Object> mockHandler = MockUtil.getMockHandler(mock);
        InvocationContainerImpl invocationContainer = (InvocationContainerImpl)mockHandler.getInvocationContainer();
        Collection<Stubbing> stubbings = invocationContainer.getStubbingsAscending();
        for (Stubbing stubbing : stubbings) {
            Invocation invocation = stubbing.getInvocation();
            System.out.println("invocation: " + invocation);
            Location location = invocation.getLocation();
            System.out.println("location: " + location);
        }

        SomethingWithStaticMethod.doStaticVoid();
        //SomethingWithStaticMethod.doStaticOne();
        //SomethingWithStaticMethod.doStaticTwo();
    }
}

This test can be dropped into a powermock working dir and executed. It will pass but should fail. The last two commented lines should be required for it to pass but they are not. The println statements show that the location for each invocation is calculated to be the same (inside the powermock library).

The root cause is the StackTraceCleaner in StackTraceCleanerProvider not removing all the elements in the stack that need to be removed to get to the element that represents the line where the invocation is really declared in the test.

I have made a change to the cleaner and have added a unit test that I will submit as a pull request after creating this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant