5

I'm using ShrinkWrap to start Jetty server in my integration tests.

Problem:

When I start my test jetty-server and than make mockup of my controller - mockup doesn't work! I suggest that the reason is different classloaders: JMockit - AppClassLoader, Jetty - WebAppClassLoader.

Question:

How to make mocking works fine?

P.S. I've googled that -javaagent:jmockit.jar option may help. But it doesn't. Is it necessary for maven project based on 1.7 jdk?

ADDITION:

I've written demo to illustrate my problem. You can find it by the reference.

About my demo:

Except of ten stokes of code, it is identical to those project. I've only added JMockit and a single mock to illustrate the problem.

You should see JettyDeploymentIntegrationUnitTestCase.requestWebapp method: in those method we make mock which doesn't work.

You can check that Jetty & JMockit loads classes by siblings classloaders, so JMockit simply doesn't see Jetty's classes

URLClassLoader
|
|-Launcher$AppClassLoader
|-WebAppClassLoader
VB_
  • 45,112
  • 42
  • 145
  • 293
  • 1
    I have no experience with ShrinkWrap, but if you can provide an example Maven project which reproduces the problem, I am willing to verify it. – Rogério Sep 23 '13 at 11:31
  • See addition section of my question. And thankyou for will to help – VB_ Sep 23 '13 at 12:17
  • If it is interesting to you, this problem is absent on Tomcat embedded server, because it use the same ClassLoader as JMockit – VB_ Sep 23 '13 at 13:13

1 Answers1

3

The JUnit test in the example project is attempting to mock the ForwardingServlet class. But, in this scenario with an embedded Jetty web server, there are actually two instances of this class, both loaded in the same JVM but through different classloaders.

The first instance of the class is loaded by the regular classloader, through which classes are loaded from the thread that starts the JUnit test runner (AppClassLoader). So, when ForwardingServlet appears in test code, it is the one defined in this classloader. This is the class given to JMockit to mock, which is exactly what happens.

But then, a copy of ForwardingServlet is loaded inside the deployed web app (from the ".class" file in the file system, so not affected by the mocking as applied by JMockit, which is in-memory only), using Jetty's WebAppClassLoader. This class is never seen by JMockit.

There are two possible solutions to this issue:

  1. Somehow get the class object loaded by WebAppClassLoader and then mock it by calling the MockUp(Class) constructor.

  2. Configure the Jetty server so that it does not use a custom classloader for the classes in the web app.

The second solution is the easiest, and can be done simply by adding the following call on the ContextHandler object created from the WebArchive object, before setting the handler into the Jetty Server object:

handler.setClassLoader(ClassLoader.getSystemClassLoader());

I tested this and it worked as expected, with the @Mock doGet(...) method getting executed instead of the real one in ForwardingServlet.

Rogério
  • 16,171
  • 2
  • 50
  • 63
  • 1
    @Rogerio would you know how to set the classloader for the webapp in Tomcat? I am dealing with the same issue in which the class loaded via the webapp is not reachable by JMockit. Thanks – beluchin May 14 '15 at 20:06