Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 399
chaliasos Avatar asked Jul 01 '14 11:07

chaliasos


2 Answers

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>
like image 97
Grigoris Grigoriadis Avatar answered Oct 18 '22 10:10

Grigoris Grigoriadis


I couldn't get the answer https://stackoverflow.com/a/24512682/156477 from @Grigoris working, although his explanation for why it is happening is correct.

In the end I went for the approach below which exposes a special setter to insert the mock object. Not as 'clean' as the approach above, but worth the tradeoff of exposing the apiProvider I wanted to mock so I could write some tests..

public MyAPITest extends JerseyTest {

    // Declare instance of the API I want to test - this will be instantiated in configure()
    MyAPI myAPI;

    @Override
    protected ResourceConfig configure()
    {  
        MockitoAnnotations.initMocks(this);
        myAPI = new MyAPI();

        ResourceConfig resourceConfig = new ResourceConfig();
        resourceConfig.register(MyAPI).property("contextConfig", new ClassPathXmlApplicationContext("classpath:spring.testHarnessContext.xml"));
        return resourceConfig;
    }

    @Mock
    private MyAPIProvider mockAPIProvider;

    @Before
    public void before() {
        myAPI.setMockProvider(mockAPIProvider);
    }


    @Test
    public void test() {

        // I can now define the mock behaviours and call the API and validate the outcomes
        when(mockAPIProvider....)
        target().path("....)            
    }
}
like image 1
Kevin Avatar answered Oct 18 '22 11:10

Kevin