Is it possible to mock a static method on a final class using a PowerMockRule instead of the PowerMockRunner?
Asked Answered
V

3

8

According to the PowerMock docs, I should be able to run using a PowerMockRule instead of @RunWith(PowerMockRunner.class) and get the same results.

I seem to have found a case where this isn't true.

The below sample runs fine:

package com.test.powermockstatics;

import static org.junit.Assert.assertEquals;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

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

final class FinalClassWithStaticCall {
  public static int getIntStatic() {
    return 1;
  }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClassWithStaticCall.class)
public class TestStaticMockingWithoutPowerMockRunner {
  @Test
  public void testStaticCall() {
    mockStatic(FinalClassWithStaticCall.class);
    when(FinalClassWithStaticCall.getIntStatic()).thenReturn(2);

    assertEquals(FinalClassWithStaticCall.getIntStatic(), 2);
  }
}

But when switched to a rule like so:

package com.test.powermockstatics;

import static org.junit.Assert.assertEquals;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

import org.junit.Rule;
import org.junit.Test;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.agent.PowerMockAgent;
import org.powermock.modules.junit4.rule.PowerMockRule;

final class FinalClassWithStaticCall {
  public static int getIntStatic() {
    return 1;
  }
}

@PrepareForTest(FinalClassWithStaticCall.class)
public class TestStaticMockingWithoutPowerMockRunner {
  static {
    PowerMockAgent.initializeIfNeeded();
  }

  @Rule
  public PowerMockRule rule = new PowerMockRule();

  @Test
  public void testStaticCall() {
    mockStatic(FinalClassWithStaticCall.class);
    when(FinalClassWithStaticCall.getIntStatic()).thenReturn(2);

    assertEquals(FinalClassWithStaticCall.getIntStatic(), 2);
  }
}

I get the following exception:

java.lang.IllegalArgumentException: Cannot subclass final class class com.test.powermockstatics.FinalClassWithStaticCall at org.mockito.cglib.proxy.Enhancer.generateClass(Enhancer.java:447) at org.mockito.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:217) at org.mockito.cglib.proxy.Enhancer.createHelper(Enhancer.java:378) at org.mockito.cglib.proxy.Enhancer.createClass(Enhancer.java:318) at org.mockito.internal.creation.jmock.ClassImposterizer.createProxyClass(ClassImposterizer.java:110) at org.mockito.internal.creation.jmock.ClassImposterizer.imposterise(ClassImposterizer.java:62) at org.powermock.api.mockito.internal.mockcreation.MockCreator.createMethodInvocationControl(MockCreator.java:111) at org.powermock.api.mockito.internal.mockcreation.MockCreator.mock(MockCreator.java:60) at org.powermock.api.mockito.PowerMockito.mockStatic(PowerMockito.java:70) at com.test.powermockstatics.TestStaticMockingWithoutPowerMockRunner.testStaticCall(TestStaticMockingWithoutPowerMockRunner.java:30) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:49) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

I am following the recommendation from the docs to:

put powermock-module-junit4-rule-agent before junit in the classpath

Does anyone know the official word if this is a bug in PowerMock or the desired behavior (i.e., you simply can't mock a static method on a final class using a PowerMockRule)?

EDIT:

Please see the clarifying details in the comments under Gábor Lipták's answer. I do not want to use a statically loaded Agent, since it appears the dynamically loaded Agent ought to be capable of getting the job done?

I know starting the agent statically will work. (Unfortunately this is not an option in my project.) So does anyone know if the failure of the dynamically loaded Agent is a bug in PowerMock? Or a known limitation; and why?

Volition answered 15/9, 2014 at 14:42 Comment(0)
A
15

You need to prepare the class for test!

@PrepareForTest(MyFinalClass.class)

Anuradhapura answered 17/4, 2015 at 10:2 Comment(1)
Why should I do this?Amador
A
2

For mocking final classes classpath is not enough. You need JVM agent.

According to the docs:

In some cases (such as mocking final classes) it may be necessary to load the PowerMock agent eagerly in Maven in order for the tests to work in Surefire. If you experience this please add the following to your pom.xml:

Needed JVM argument to mock final classes:

-javaagent:${settings.localRepository}/org/powermock/powermock-module-javaagent/1.5.6/powermock-module-javaagent-1.5.6.jar
Abingdon answered 15/9, 2014 at 19:47 Comment(6)
There is no Maven involved here though.Volition
Then download the jar, and type command: java .... -javaagent:pathtojar/powermock-module-javaagent-1.5.6.jar . Thats all.Helico
I agree I need an Agent. Agents can be added dynamically, if you look at the code in github.com/jayway/powermock/blob/master/modules/module-impl/… for the initializeIfNeeded method, you'll see it dynamically attaches an Agent to the running JVM process. I unfortunately have to load the Agent dynamically, and cannot add arguments to statically load the agent upon JVM startup. The docs however, make it sound like this (static loading of the agent) is only necessary for use with Maven.Volition
You are correct that when I toss this argument into my JVM args for my JUnit launch, mocking proceeds correctly. However, I do not know if this (static Agent setup) is a bug or a feature. Dynamically loaded Agents are perfectly capable of transforming already loaded classes (since the FinalClassWithStaticCall class in my example would be loaded prior to the Agent being dynamically loaded) - and the PowerMock agent jar's MANIFEST.MF in fact DOES specify Can-Retransform-Classes: true Can-Redefine-Classes: true so It ought to be capable of this.Volition
Additionally, when tracing through the code to dynamically load the Agent, it appears to be firing the SAME EXACT initialize method that static loading calls (PowerMockAgent.java, line 62). So I think my question still stands: is this intended behavior or a bug? It seems to me, after looking at the source, MORE likely to be a bug at this point - why isn't the dynamic Agent stripping the final modifiers off my loaded classes if it appears to say it can? I will add these additional clarifying details to the description. Thanks for your help - getting these details right is important!Volition
Just write an email on Powermock authors, surely they will answer. I do not know Powermock that deep.Helico
L
1

I had the same symptom and could solve it by extending the test class from PowerMockTestCase.

public class NetworkManagerUtilsTest extends PowerMockTestCase {

Not sure if this solution is applicable here.

Lavatory answered 7/12, 2016 at 10:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.