JMockit + Jetty in functional tests

2019-04-29 10:02发布

问题:

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

回答1:

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.