Search code examples
javaspringunit-testingjunitjersey

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?


Solution

  • 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>