powermock / powermock

PowerMock is a Java framework that allows you to unit test code normally regarded as untestable.
Apache License 2.0
4.15k stars 585 forks source link

StackOverflowError - PowerMock with Mockito #732

Open ziccardi opened 7 years ago

ziccardi commented 7 years ago

PowerMock : Version 1.6.4 OS: Mac OS Sierra

When you try to test a method that invokes a parent method with the same name, you get a StackOverflowError.

Follows code to reproduce the issue:

UselessParent.java:

public class UselessParent {
    public  void testMe() {
        return;
    }
}

ClassToBeTested.java:

public class ClassToBeTested extends UselessParent{

    public final void testMe() {
        super.testMe();
    }
}

MyTest.java

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({ ClassToBeTested.class })
public class MyTest {
    @Test
    public void testMethod() {
        ClassToBeTested c = PowerMockito.spy(new ClassToBeTested());
        c.testMe();
    }
}

When you run the test you get a StackOverflowError:

java.lang.StackOverflowError
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at org.powermock.core.classloader.MockClassLoader.loadModifiedClass(MockClassLoader.java:178)
    at org.powermock.core.classloader.DeferSupportingClassLoader.loadClass(DeferSupportingClassLoader.java:70)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
ziccardi commented 7 years ago

The same happens with PowerMock 1.6.6.

ziccardi commented 7 years ago

I think this is related to #698, however the stack overflow happens when invoking it first time.

bedrin commented 6 years ago

Try @PrepareOnlyThisForTest instead of @PrepareForTest

antlechner commented 5 years ago

I came across the same problem with PowerMock 1.6.5. I can reproduce the bug in the example above, using @PrepareOnlyThisForTest doesn't help, but the test passes if I replace @PrepareForTest({ ClassToBeTested.class }) with @PrepareForTest({ UselessParent.class })

Something similar happens with covariant return types: Foo.java

public class Foo extends Bar {
    public Integer foo() {
        return 2;
    }
}

Bar.java

public class Bar {
    public Object foo() {
        return 1;
    }
}

MyTest.java

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)
public class MyTest {
  @PrepareForTest( { Foo.class } )
  @Test
  public void testFoo() {
    final Foo objectUnderTest = PowerMockito.mock(Foo.class);
    PowerMockito.when(objectUnderTest.foo()).thenCallRealMethod();
    final Integer retval = objectUnderTest.foo();
  }
}

gives the same error. After changing the return type of Bar.foo from Object to Integer, MyTest.testFoo passes. And just as in the above example, if I change the annotation from @PrepareForTest( { Foo.class } ) to @PrepareForTest( { Bar.class } ) (leaving Object as the return type of Bar.foo), the test also passes.

Does this mean that when using PowerMock to mock a subclass, the @PrepareForTest annotation should always contain only its parent, and not the class itself? Or should both annotations always work and this is a bug?

antlechner commented 5 years ago

At least in my above example, nothing is actually mocked and Powermock is not needed, but this modification seems more worrying: Foo.java

public class Foo extends Bar {
    public Integer foo() {
        return bar();
    }
}

Bar.java

class Bar {
    public Object foo() {
        return 1;
    }
    public Integer bar() {
        return 5;
    }
}

MyTest.java

import org.junit.Assert;
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)
public class TestForFoo_foo___Ljava_lang_Integer_ {

/*
 * Test generated by Diffblue Deeptest.
 * This test case covers the entire method.
 */
@PrepareForTest( { Foo.class } )
@Test
public void fooOutputPositive000d2dc46aa55a955b9() {

  // Arrange
  final Foo objectUnderTest = PowerMockito.mock(Foo.class);
  PowerMockito.doCallRealMethod().when(objectUnderTest);
  objectUnderTest.foo();
  PowerMockito.when(objectUnderTest.bar()).thenReturn(100);

  // Act
  final Integer retval = objectUnderTest.foo();

  // Assert result
  Assert.assertEquals(new Integer(100), retval);

}
}

Here, the method Foo.bar is being mocked, which is inherited from Bar. But since Bar is not public, bar() is actually copied into the bytecode of Foo, so manipulating the bytecode of Foo (rather than just Bar) seems to make sense here? But again, this test causes the StackOverflowError. And again, replacing @PrepareForTest( { Foo.class } ) with @PrepareForTest( { Bar.class } ) makes the test pass.

I've tried Powermock versions 1.6.5, 1.6.6, 1.7.0 and 1.7.4 now, they all give me the same error.

lmarkes commented 5 years ago

Powermock 2.0.2 and Mockito 2.28.2 I resolved this issue by removing the parent class from @PrepareForTest.