Pure JerseyTest without letting Spring messing wit

2019-07-17 12:09发布

问题:

I am having hard time with JerseyTest and Spring. Previously in non-Spring Java projects what I usually did for my REST APIs was to extend my Test from JerseyTest, mocking the Service classes and simply (unit)testing my REST API. Now I'm using spring in my project where in my REST resource classes the Services are annotated with @Autowired. Now that I'm using the same scenario. Spring jumps in and nags about stuff like lack of applicationcontext.xml. I do want to use spring in my production but for my unit test I don't need my test know anything about Spring and all its autowiring and classpath annotation processing! How can I get this right? The classes look like this:

public class RESTResource{
  @Autowired
  MyService service;

  @GET
  public Response getSomeStuff(){
    ...
    service.getStuff()
  }

}

And here is the Test class

public class RESTResourceTest extends JerseyTest{

   private Service service;
   @Override
   public Application configure(){
      RESTResource resource = new RESTResource();
      service = Mockito.mock(Service.class);
      resource.setService(service);
      ResourceConfig config = new ResourceConfig();

      config.register(resource);
      return config;
   }
}

This is the stacktrace:

org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [applicationContext.xml]; nested exception is java.io.FileNotFoundException: class path resource [applicationContext.xml] cannot be opened because it does not exist
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:344)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:252)
    at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127)
    at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93)
    at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129)
    at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:538)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:452)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:93)
    at org.glassfish.jersey.server.spring.SpringComponentProvider.createXmlSpringConfiguration(SpringComponentProvider.java:173)
    at org.glassfish.jersey.server.spring.SpringComponentProvider.createSpringContext(SpringComponentProvider.java:164)
    at org.glassfish.jersey.server.spring.SpringComponentProvider.initialize(SpringComponentProvider.java:99)
    at org.glassfish.jersey.server.ApplicationHandler$4.get(ApplicationHandler.java:408)
    at org.glassfish.jersey.server.ApplicationHandler$4.get(ApplicationHandler.java:399)
    at org.glassfish.jersey.internal.util.collection.Values$LazyValueImpl.get(Values.java:340)
    at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:350)
    at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:347)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
    at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:347)
    at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:299)
    at org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory$InMemoryTestContainer.<init>(InMemoryTestContainerFactory.java:77)
    at org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory$InMemoryTestContainer.<init>(InMemoryTestContainerFactory.java:63)
    at org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory.create(InMemoryTestContainerFactory.java:111)
    at org.glassfish.jersey.test.JerseyTest.createTestContainer(JerseyTest.java:277)
    at org.glassfish.jersey.test.JerseyTest.setUp(JerseyTest.java:609)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.io.FileNotFoundException: class path resource [applicationContext.xml] cannot be opened because it does not exist
    at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:172)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:330)
    ... 56 more

P.S. I'm using spring boot.

回答1:

What you can do is to simply exclude the jersey-spring3 using the the sure-fire plugin for test phase.

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <classpathDependencyExcludes>
                        <classpathDependencyExclude>
                            org.glassfish.jersey.ext:jersey-spring3
                        </classpathDependencyExclude>
                    </classpathDependencyExcludes>
                </configuration>
            </plugin> 


回答2:

Here is some code snippets that might help in your case : Option 1. Your REST resource class :

@Component
@Path("/api/helloworld")
public class RESTResource{
@Autowired
MyService service;

@GET
@Produces(MediaType.TEXT_PLAIN)
public String helloMessage() {
    return "Hello World Jersey Way!";
}

}
  1. Your test config class :

    @Component
    public class JerseyConfig extends ResourceConfig {
    
    /**
     * In constructor we can define Jersey Resources &amp; Other Components
    */
    public JerseyConfig() {
        register(RESTResource.class);
    }
    }
    

3.Your test base class :

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)//NOTE : Application is a spring boot main class,or if you have specific configuration class similar to test config class above use @ContextConfiguration(classes = MyServiceSpringConfig.class) 
public class RESTResourceTest extends JerseyTest {

@Override
protected Application configure() {
    return new JerseyConfig();
}

@Test
public void contextLoads() {
}

@Test
public void someTest() throws Exception {
    // Very useful test
}


}

Option 2 :

@Priority(value = 1)
public class MySpringWebInitializer implements WebApplicationInitializer
{
    @Override
    public void onStartup(ServletContext container)
    {
        //Tell jersey-spring3 the context is already initialized
        container.setInitParameter("contextConfigLocation", "NOTNULL");
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(RESTResource.class);
        container.addListener(new ContextLoaderListener(appContext));
    }
}

Also,for spring boot you can run test cases using an active profile or a config name ,pass runtime VM args with -Dspring.config.name=test for running test cases etc..

Hope this help and the rest is self explanatory.



回答3:

The better way to integrate JerseyTest with Spring
  • No inheritance from JerseyTest in junit test class
  • No dependencies on Jersey in junit test class, just pure jax-rs dependency
  • perfectly integrate SpringJUnit4ClassRunner and @ContextConfiguration

My solution is hosted on github. Hope this help for you.