Schaliasos Schaliasos - 25 days ago 10
Java Question

Force Jersey to read mocks from JerseyTest

I want to test a Resourse with JerseyTest. I have created the following test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:testApplicationContext.xml")
public class ResourceTest extends JerseyTest
{
@Configuration
public static class Config
{
@Bean
public AObject aObject()
{
return mock(AObject.class);
}
}

@Autowired
public AObject _aObject;

@Test
public void testResource()
{
// configouring mock _aObject

Response response = target("path");
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
}


@Override
protected Application configure()
{
return new ResourceConfig(Resource.class).property("contextConfigLocation", "classpath:testApplicationContext.xml");
}
}


My Resource also has an AObject reference with
@Autowired
annotation.

My problem is that my
JerseyTest
and the
Resource
(that is configured by the test) have different instances for the Mock object. In the console I see that the
testApplicationContext.xml
is loaded twice, once for the test and one for the Resource.

How can I force jersey to use the same mock?

Answer

After debugging the jersey-spring3 (version 2.9.1) library it seems that the problem lies in the SpringComponentProvider.createSpringContext

private ApplicationContext createSpringContext() {
    ApplicationHandler applicationHandler = locator.getService(ApplicationHandler.class);
    ApplicationContext springContext = (ApplicationContext) applicationHandler.getConfiguration().getProperty(PARAM_SPRING_CONTEXT);
    if (springContext == null) {
        String contextConfigLocation = (String) applicationHandler.getConfiguration().getProperty(PARAM_CONTEXT_CONFIG_LOCATION);
        springContext = createXmlSpringConfiguration(contextConfigLocation);
    }
    return springContext;
}

It checks if a property named "contextConfig" exists in the application properties and if not it initializes the spring application context. Even if you initialized a spring application context in your tests, jersey will create another context and use that one instead. So we have to somehow pass the ApplicationContext from our tests in the Jersey Application class. The solution is the following:

@ContextConfiguration(locations = "classpath:jersey-spring-applicationContext.xml")
public abstract class JerseySpringTest
{
    private JerseyTest _jerseyTest;

    public final WebTarget target(final String path)
    {
        return _jerseyTest.target(path);
    }

    @Before
    public void setup() throws Exception
    {
        _jerseyTest.setUp();
    }

    @After
    public void tearDown() throws Exception
    {
        _jerseyTest.tearDown();
    }

    @Autowired
    public void setApplicationContext(final ApplicationContext context)
    {
        _jerseyTest = new JerseyTest()
        {
            @Override
            protected Application configure()
            {
                ResourceConfig application = JerseySpringTest.this.configure();
                application.property("contextConfig", context);

                return application;
            }
        };
    }

    protected abstract ResourceConfig configure();
}

The above class will take the application context from our tests and pass it to the configured ResourceConfig, so that the SpringComponentProvider will return the same application context to jersey. We also use the jersey-spring-applicationContext.xml in order to include jersey specific spring configuration.

We cannot inherit from JerseyTest because it initializes the Application in the constructor before the test application context is initialized.

You can now use this base class to create your tests for example

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:testContext.xml")
public class SomeTest extends JerseySpringTest
{
     @Autowired
     private AObject _aObject;

     @Test
     public void test()
     {
          // configure mock _aObject when(_aObject.method()).thenReturn() etc...

         Response response = target("api/method").request(MediaType.APPLICATION_JSON).get();
         Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
     }

     @Override
     protected ResourceConfig configure()
     {
        return new ResourceConfig(MyResource.class);
     }
}

In testContext.xml add the following definition in order to inject a mock AObject.

<bean class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.yourcompany.AObject" />
</bean>