gordonad / powermock

Automatically exported from code.google.com/p/powermock
0 stars 0 forks source link

Can't mock member class enums #95

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
REPRO: Run "mvn test" in the attached Maven project.

The project consists of an "App" class and a "TestMyEnum" class, where
TestMyEnum was copied from the Google Groups link here:
http://groups.google.com/group/powermock/browse_thread/thread/86aac968c8b466b7/9
72efd88f3c13fcd

The "App" class contains an enum as a member class, like this:

public class App {
  public enum Planet { ... }
}

EXPECTED: Test should pass.  (It works if you refactor Planet from a member
class to a top level class.) 

ACTUAL: Test fails, with the error: java.lang.IllegalArgumentException:
Cannot subclass final class class mockEnumBug.App$Planet
        at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
        at
net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.jav
a:25)
        at
net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
        at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
        at net.sf.cglib.proxy.Enhancer.createClass(Enhancer.java:317)
        at
org.powermock.api.easymock.internal.signedsupport.SignedSupportingClassProxyFact
ory.createProxy(SignedSupportingClassProxyFactory.java:155)
        at org.easymock.internal.MocksControl.createMock(MocksControl.java:40)
        at org.powermock.api.easymock.PowerMock.doMock(PowerMock.java:2089)
        at org.powermock.api.easymock.PowerMock.createMock(PowerMock.java:92)
        at mockEnumBug.TestMyEnum.testEnum(TestMyEnum.java:18)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.jav
a:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59)
        at
org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$2.
runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:217)
        at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:79)
        at
org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadi
e.java:87)
        at
org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$2.
runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:207)
        at
org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77)
        at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42)
        at
org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.in
vokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:202)
        at
org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.ru
nMethods(PowerMockJUnit44RunnerDelegateImpl.java:156)
        at
org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.
run(PowerMockJUnit44RunnerDelegateImpl.java:130)
        at
org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
        at
org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
        at
org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.ru
n(PowerMockJUnit44RunnerDelegateImpl.java:128)
        at
org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run
(JUnit4TestSuiteChunkerImpl.java:112)
        at
org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.
run(AbstractCommonPowerMockRunner.java:44)
        at
org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:62)
        at
org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(Abstra
ctDirectoryTestSuite.java:140)
        at
org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirec
toryTestSuite.java:127)
        at org.apache.maven.surefire.Surefire.run(Surefire.java:177)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.jav
a:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at
org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBoote
r.java:338)
        at
org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:997)

Using PowerMock 1.2, Java 1.6, Windows XP SP3

Original issue reported on code.google.com by DanFabul...@gmail.com on 12 Mar 2009 at 8:02

Attachments:

GoogleCodeExporter commented 8 years ago
I've investigated this a little and it seems like JavaAssist ignores the 
removal of
the final modifier for inner classes (enum = static final class). This thread 
may
provide some hints:
http://www.jboss.org/index.html?module=bb&op=viewtopic&p=4090308#4090308.

Original comment by johan.ha...@gmail.com on 17 Mar 2009 at 2:06

GoogleCodeExporter commented 8 years ago
Hope to get a solution for this as i have a similar situation with requirement 
to
mock  xmlbeans (generated) classes which contain public static final classes.
public interface NameNamingAttributeEnum
    extends XmlString
{
   public static final class Enum extends StringEnumAbstractBase
    {

Original comment by rajmel...@googlemail.com on 8 Apr 2009 at 12:28

GoogleCodeExporter commented 8 years ago
Could the problem be that you're not allowed to remove the "final" keyword from 
inner
Enums according the java specification? Seems like a long-shot though. 

One (difficult) solution to the problem would be to create a substitute/replica 
outer
Enum class and copy the structure of the inner Enum class  and then delegate all
field access and method calls to this fictive class (just the way mocking of 
final
system classes works now). Could work...

Original comment by johan.ha...@gmail.com on 12 Apr 2009 at 10:56

GoogleCodeExporter commented 8 years ago

Original comment by jan.kron...@gmail.com on 27 Apr 2009 at 4:56

GoogleCodeExporter commented 8 years ago
These are generated classes so found a way to use the realclasses rather than 
mock
them at the moment.Lucky to get past at the moment.

Original comment by rajmel...@googlemail.com on 27 Apr 2009 at 5:00

GoogleCodeExporter commented 8 years ago

Original comment by johan.ha...@gmail.com on 19 Sep 2009 at 7:41

GoogleCodeExporter commented 8 years ago
Is this issue fixed in the 1.3.5 version? I get the same error message as above 
when
I try to mock the Settings.Secure class in Android 1.6.

BR,
Christoffer

Original comment by toff...@gmail.com on 14 Dec 2009 at 2:09

GoogleCodeExporter commented 8 years ago
Could you try with Javassist 3.11 and see if it works?

Original comment by johan.ha...@gmail.com on 14 Dec 2009 at 2:14

GoogleCodeExporter commented 8 years ago
Replaced 3.10 with 3.11 and tried again. Same result as before unfortunately.

BR,
Christoffer

Original comment by toff...@gmail.com on 14 Dec 2009 at 2:23

GoogleCodeExporter commented 8 years ago
Ok thanks for trying it out. Last time I tried to investigate this my 
conclusion was
that either it's not allowed to remove the final modifier or it was a bug in
Javassist. But I could of course very well be wrong. If you have any idea then 
please
let us know.

Original comment by johan.ha...@gmail.com on 14 Dec 2009 at 6:05

GoogleCodeExporter commented 8 years ago
This works in JMockit so it should be legal from a byte-code perspective to 
remove the 
final modifier. I should bring this up with the Javassist team.

Original comment by johan.ha...@gmail.com on 22 Dec 2009 at 7:36

GoogleCodeExporter commented 8 years ago
JMockit does not remove the final modifier, though. It simply redefines method 
bodies 
belonging to the enum class. (The only case where JMockit changes modifiers is 
when 
mocking a native method.)
I suspect the solution will be the one you described in comment 3.

Original comment by rliesenf...@gmail.com on 22 Dec 2009 at 12:29

GoogleCodeExporter commented 8 years ago
Ah thanks for clarifying this. Just for curiosity, do you know if it ought to 
be 
possible to remove the final modifier of an enum? I don't see why you shouldn't 
be 
allowed to remove the final modifier from a static final inner class. 

Original comment by johan.ha...@gmail.com on 22 Dec 2009 at 6:57

GoogleCodeExporter commented 8 years ago
I never tried to remove "final" before, but a quick experiment with a test 
mocking 
"MyEnum" gave me the following result when attempting to subtract ACC_ENUM from 
the 
"access" argument:

java.lang.UnsupportedOperationException: class redefinition failed: attempted 
to 
change the class modifiers
    at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)

So, I would say it definitely isn't supported.

Original comment by rliesenf...@gmail.com on 23 Dec 2009 at 11:09

GoogleCodeExporter commented 8 years ago
Actually, my previous comment is mistaken. Apart from the fact that it should 
have been 
ACC_FINAL instead of ACC_ENUM (which still causes the same exception to be 
thrown), it 
only applies to "class redefinition", which isn't the PowerMock approach. 
During class 
definition, it may be valid to remove "final" for an enum, but I don't really 
know.

Original comment by rliesenf...@gmail.com on 23 Dec 2009 at 11:33

GoogleCodeExporter commented 8 years ago

Original comment by johan.ha...@gmail.com on 22 Jul 2010 at 9:16

GoogleCodeExporter commented 8 years ago
Issue 304 has been merged into this issue.

Original comment by johan.ha...@gmail.com on 6 Jan 2011 at 12:16

GoogleCodeExporter commented 8 years ago
I have enclosed the following patch that fixes this issue within PowerMock. 
Included in the patch are 3 test cases that without this patch fail to work. 
Tested on Sun/Oracle Linux x86_64 1.6.0_21.

The change comes down to the InnerClasses attribute [1] in the class byte code. 
It seems that even though the class proper is marked as non-final, the JVM 
would see that the class should be final based upon the info in this attribute 
and as a result the class would remain final. The patch looks for the 
InnerClasses attribute on each class, and if the attribute exists, resets the 
final access modifier for the class being edited.

Patch was made against svn revision 1570 at 
http://powermock.googlecode.com/svn/trunk.

[1] 
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#80000

Original comment by nairb...@gmail.com on 21 Jan 2011 at 12:36

Attachments:

GoogleCodeExporter commented 8 years ago
Awesome!! I've applied the patch and committed it to trunk. I've also verified 
that it works for private static final inner classes. 

Thanks A LOT for your help. As you may know this has been an issue for so long 
and it's really nice that it has finally been resolved.

Original comment by johan.ha...@gmail.com on 21 Jan 2011 at 7:37

GoogleCodeExporter commented 8 years ago

Original comment by johan.ha...@gmail.com on 21 Jan 2011 at 8:32

GoogleCodeExporter commented 8 years ago
Excellent! (I just updated my comparison matrix to reflect this.)

Original comment by rliesenf...@gmail.com on 2 Mar 2011 at 8:06