springunit-testingjunitjerseyjersey-2.0

How to in-memory unit test Spring-Jersey


I'm working with Spring-Jersey3 and cannot figure out how to unit test the RESTFul API with Spring beans

Controller

package com.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.service.DataSource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("test")
@Component
public class SpringController {
    @Autowired
    private DataSource datasource;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getHello() {
        return new String(datasource.load());
    }
}

Service Interface

package com.service;

public interface DataSource {
    public String load();
}

Service Implementation

package com.service;

import org.springframework.stereotype.Repository;

@Repository
public class DataSourceImpl implements DataSource {

    @Override
    public String load() {
        return "Hello";
    }
}

ResourceRegister.java (Jersey resource register)

package com.component;

import org.glassfish.jersey.server.ResourceConfig;
import com.controller.SpringController;

public class ResourceRegister extends ResourceConfig {

    public ResourceRegister () {
        register(SpringController.class);
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<servlet>
  <servlet-name>Jersey</servlet-name>
  <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
  <param-name>javax.ws.rs.Application</param-name>
  <param-value>com.component.ResourceRegister</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>Jersey</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

serviceContext.xml (Application Context)

<?xml version="1.0" encoding="UTF-8"?>

<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.service" />
<context:component-scan base-package="com.controller" />

</beans>

Unit test <<-- I really have no Idea how to test this

public class test extends JerseyTest {
    public test() {
        super("com.service", "com.controller");
    }

    @Override
    protected AppDescriptor configure() {
        return new WebAppDescriptor.Builder("com.service","com.controller")
               .contextParam("contextConfigLocation", "classpath:serviceContext.xml")
               .contextPath("/rest")
               .servletClass("org.glassfish.jersey.servlet.ServletContainer.class")
               .initParam("javax.ws.rs.Application", "com.component.ResourceRegister")
               .build();
    }

    @Test
    public void test() {
        Client client = new Client();
        WebResource resource = client.resource("test");

        ClientResponse response = resource.post(ClientResponse.class);

        assertEquals(200, resposne.getStatus());
    }
}

Project Source Code

Problem : Dependency injection returns null


Solution

  • A few things I would fix:

    Here is a refactor that works.

    Dependency (I only added this dependency, and didn't take anything out, as your GitHub project didn't include anything test related, as your code example above does)

    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
        <version>2.15</version>
    </dependency>
    

    Test

    import com.component.ResourceRegister;
    import org.glassfish.jersey.servlet.ServletContainer;
    import org.glassfish.jersey.test.DeploymentContext;
    import org.glassfish.jersey.test.JerseyTest;
    import org.glassfish.jersey.test.ServletDeploymentContext;
    import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
    import org.glassfish.jersey.test.spi.TestContainerFactory;
    import org.junit.Assert;
    import org.junit.Test;
    import org.springframework.web.context.ContextLoaderListener;
    
    public class SpringTest extends JerseyTest {
        
        @Override
        protected TestContainerFactory getTestContainerFactory() {
            return new GrizzlyWebTestContainerFactory();
        }
        
        @Override
        protected DeploymentContext configureDeployment(){
            return ServletDeploymentContext
                    .forServlet(new ServletContainer(new ResourceRegister()))
                    .addListener(ContextLoaderListener.class)
                    .contextParam("contextConfigLocation", "classpath:applicationContext.xml")
                    .build();
        }
        
        @Test
        public void test() {
            String response = target("test").request().get(String.class);
            Assert.assertEquals("Hello", response);
            System.out.println(response);
        }  
    }
    

    For those not use an xml context file, you can use an annotation config application context, and add it as an init param

    return ServletDeploymentContext
            .forServlet(new ServletContainer(new ResourceRegister()))
            .addListener(ContextLoaderListener.class)
            .initParam("contextConfig", new AnnotationConfigApplicationContext(YourSpringConfig.class))
            .build();
    

    Other Resources:


    UPDATE

    So after a few more test, here are a couple interesting things I discovered

    One:

    With the above dependency, even if we don't configure the DeploymentContext, and just override Application configure() in the JerseyTest, it will still work. Can't really explain it, but it appears the descriptor is still picked up.

    import javax.ws.rs.core.Application;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.test.JerseyTest;
    import org.junit.Assert;
    import org.junit.Test;
    
    public class SpringTest extends JerseyTest {
        
        @Override
        public Application configure() {
            return new ResourceConfig().packages("com.controller");
        }
        
        @Test
        public void test() {
            String response = target("test").request().get(String.class);
            Assert.assertEquals("Hello", response);
            System.out.println(response);
        }  
    }
    

    Two:

    Even if we get rid of the above dependency (grizzly) and use the in-memory dependency, the same simple previous test works. The documentation states

    In-Memory container is not a real container. It starts Jersey application and directly calls internal APIs to handle request created by client provided by test framework. There is no network communication involved. This containers does not support servlet and other container dependent features, but it is a perfect choice for simple unit tests.

    So I am not completely sure what Servlet features they are referring to, as this test still works

    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-inmemory</artifactId>
        <version>2.15</version>
    </dependency>
    

    What I don't understand in particular, is this statement

    "There is no network communication involved"

    because when I run the test, I see a log

    INFO: Creating InMemoryTestContainer configured at the base URI http://localhost:9998/