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

Odd mock interception with @PrepareForTest and PowerMockObjectFactory #320

Closed
johanhaleby opened this issue Jul 24, 2015 · 3 comments
Closed

Comments

@johanhaleby
Copy link
Collaborator

From flush...@gmail.com on December 16, 2010 21:05:32

Please see the attached small demo project with 5 test classes that demonstrate the problem and workaround(s).

My environment is the following:
Windows 7 Enterprise 64-bit
JDK 1.6.0_22
Maven 2.2.1
PowerMock 1.4.6
TestNG 5.14.1

SampleServlet represent the class being tested with very simple doGet method that writes "out" string to response writer.

Consider the SampleServletTestWithoutPrepare test that works fine and doesn't use any PowerMock specific extras except of 'createMock', 'replay' and 'verify'.

Once I add @PrepareForTest annotation to the test class (see SampleServletTestWithPrepare1) test fails with the following exception:

java.lang.AssertionError:
Unexpected method call write("out"):
write("out"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:45)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:73)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:92)
at $java.io.PrintWriter$$EnhancerByCGLIB$$8cbc17d1.write()
at com.volodymyrtsukur.powermock.demos.SampleServlet.doGet(SampleServlet.java:16)
at com.volodymyrtsukur.powermock.demos.SampleServletTestWithPrepare1.doGet(SampleServletTestWithPrepare1.java:37)
at com.volodymyrtsukur.powermock.demos.SampleServletTestWithPrepare1_$$_javassist_0.d1doGet(SampleServletTestWithPrepare1$$javassist_0.java)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.powermock.modules.testng.internal.PowerMockTestNGMethodHandler.invoke(PowerMockTestNGMethodHandler.java:51)
at com.volodymyrtsukur.powermock.demos.SampleServletTestWithPrepare1
$$javassist_0.doGet(SampleServletTestWithPrepare1$$_javassist_0.java)

Call to the 'write' is intercepted by EasyMock , not by PowerMock's gateway. Is it fine?

It may seem useless to specify @PrepareForTest without actually mocking statics or finals. In original test case (in the project that cannot be posted here) instances of classes with final methods were mocked. I strip this down from examples because it actually doesn't have anything to do with the actual problem. I just add @PrepareForTest, specify ObjectFactory and test code doesn't work.

Adding specific class to @PrepareForTest doesn't help (see SampleServletTestWithPrepare2 test) unless I specify the class that is actually the caller of 'write' method, i.e. SampleServlet class (see SampleServletTestWithTargetClassPrepare test). And pay attention that once I inline the servlet code into the test method it starts to pass as well (SampleServletTestWithInline).

So, it seems that each class that calls mock needs to be 'prepared'. I've seen similar rule in the documentation for expectNew but not for regular mocks that just work inside the test with annotated @PrepareForTest and PowerMockObjectFactory. Is it a problem?

In debug it may be seen that test class is prepared as well, that's why inlining the servlet code works.

Attachment: invalid-mock-interception.zip

Original issue: http://code.google.com/p/powermock/issues/detail?id=300

@johanhaleby
Copy link
Collaborator Author

From johan.ha...@gmail.com on December 26, 2010 01:08:34

Once again a big thanks for your detailed description and excellent example project! I can totally understand that what you see doesn't seem logical but it is :). I'll try to answer your questions:

Q. Why do you get a difference with @PrepareForTest when you don't need to prepare anything?
A. Actually you do prepare something when using @PrepareForTest even though you haven't specified anything. The "test case" is always automatically prepared for test when you use the @PrepareForTest annotation. The reason is that when mocking system classes you'd like to keep the same mocking syntax (read about it here: http://blog.jayway.com/2009/05/17/mocking-static-methods-in-java-system-classes/ ).

Q: Call to the 'write' is intercepted by EasyMock , not by PowerMock's gateway. Is it fine?
A: The problem is that the PrintWriter is a Java System class. PowerMock treats system classes specially because if the class would have been final there's no way for PowerMock to byte-code manipulate it (refer to the blog for info). For these classes you need to prepare the class calling the system class instead of the actual system class (see https://code.google.com/p/powermock/wiki/MockSystem ). In your case it becomes quite strange though since even if PrintWriter is a system class it is NOT final and the method you're trying to mock is not final either so it should be possible to mock it normally. How ever in the byte code only checks that the class starts with "java." (i.e. that it's a system class) and doesn't care if the class is final or that the method is final. I'll try to change this and see if we run into any implications. But right now you would have to prepare the class SampleServlet.class for test in SampleServletTestWithPrepare1 and SampleServletTestWithPrepare2 in order to make the test pass. This is because it is SampleServlet that calls the write method (which is located in a java system class).

@johanhaleby
Copy link
Collaborator Author

From johan.ha...@gmail.com on December 26, 2010 01:52:07

Fixed in r1524

Status: Fixed
Labels: Milestone-Release1.4

@johanhaleby
Copy link
Collaborator Author

From flush...@gmail.com on December 28, 2010 09:47:53

Thank you!

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

No branches or pull requests

1 participant