Oleksandr H
Oleksandr H

Reputation: 3015

Jersey Test @Autowired field in tested class is null

I have a little problem. I think this is typical question. However, I can't find good example. My application is using Jersey. And I want to test controller by client as test. Controller has private field - StudentService. When I debug test I see, that field is null. This leads to error. And I need to inject this field. I tried this: My Controller

@Path("/student")
@Component
public class StudentResourse {
    @Autowired
    private StrudentService service; // this field Spring does not set

    @Path("/getStudent/{id}")
    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Student getStudent(@PathParam("id") long id) {
         return service.get(id);
    }  
}

My JUnit test class:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:config.xml")
@TestExecutionListeners({ DbUnitTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class })
public class StudentResourseTest extends JerseyTest {
private static final String PACKAGE_NAME = "com.example.servlet";
private static final String FILE_DATASET = "/data.xml";
@Autowired
private StudentService service; // this field is setted by Spring, but I do not need this field for test

public StudentResourseTest() {
    super(new WebAppDescriptor.Builder(PACKAGE_NAME).build());
}

@Override
protected TestContainerFactory getTestContainerFactory() {
    return new HTTPContainerFactory();
}

@Override
protected AppDescriptor configure() {
    return new WebAppDescriptor.Builder("restful.server.resource")
            .contextParam("contextConfigLocation",
                    "classpath:/config.xml").contextPath("/")
            .servletClass(SpringServlet.class)
            .contextListenerClass(ContextLoaderListener.class)
            .requestListenerClass(RequestContextListener.class).build();
}

@Test
@DatabaseSetup(FILE_DATASET)
public void test() throws UnsupportedEncodingException {
        ClientResponse response = resource().path("student").path("getStudent")
                .path("100500").accept(MediaType.APPLICATION_XML)
                .get(ClientResponse.class);
        Student student = (Student) response.getEntity(Student.class);
}  }

I guees, that problem is in test class. Because, when I run my application not in test, I can directly request students and everything working fine. But when I test classes, internal field of Controller does not setted. How to fix this bug? Thanks for your answers.

This is in my config.xml

<context:component-scan base-package="com.example" />
<bean id="StudentResourse" class="com.example.servlet.StudentResourse">
    <property name="service" ref="studentService" />
</bean>
<bean id="service" class="com.example.service.StudentServiceImpl" />

Upvotes: 2

Views: 2384

Answers (2)

KVS
KVS

Reputation: 141

Reference: https://github.com/jiunjiunma/spring-jersey-test and http://geek.riffpie.com/unit-testing-restful-jersey-services-glued-together-with-spring/

Idea is to get a hold of the application context inside jersey by using ApplicationContextAware interface. There after we can grab the exact bean already created by spring, in your case, StudentService. Below example shows a mocked version of the dependency, SampleService, used to test the resource layer apis.

Resource class delegating the processing to a service layer

@Component
@Path("/sample")
public class SampleResource {

    @Autowired
    private SampleService sampleService;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path ("/{id}")
    public Sample getSample(@PathParam("id") int id) {
        Sample sample = sampleService.getSample(id);
        if (sample == null) {
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
        return sample;
    }
}

Service layer encapsulating business logic

@Service
public class SampleService {
    private static final Map<Integer, Sample> samples = new HashMap<>();

    static {
        samples.put(1, new Sample(1, "sample1"));
        samples.put(2, new Sample(2, "sample2"));
    }

    public Sample getSample(int id) {
        return samples.get(id);
    }
}

Unit test for the above resource

public class SampleResourceTest extends SpringContextAwareJerseyTest {
    private SampleService mockSampleService;

    // create mock object for our test
    @Bean
    static public SampleService sampleService() {
        return Mockito.mock(SampleService.class);
    }

    /**
     * Create our own resource here so only the test resource is loaded. If
     * we use @ComponentScan, the whole package will be scanned and more
     * resources may be loaded (which is usually NOT what we want in a test).
     */
    @Bean
    static public SampleResource sampleResource() {
        return new SampleResource();
    }

    // get the mock objects from the internal servlet context, because
    // the app context may get recreated for each test so we have to set
    // it before each run
    @Before
    public void setupMocks() {
         mockSampleService = getContext().getBean(SampleService.class);
    }

    @Test
    public void testMock() {
        Assert.assertNotNull(mockSampleService);
    }

    @Test
    public void testGetSample() {
        // see how the mock object hijack the sample service, now id 3 is valid
        Sample sample3 = new Sample(3, "sample3");
        Mockito.when(mockSampleService.getSample(3)).thenReturn(sample3);

        expect().statusCode(200).get(SERVLET_PATH + "/sample/3");
        String jsonStr = get(SERVLET_PATH + "/sample/3").asString();
        Assert.assertNotNull(jsonStr);
    }

}

SpringContextAwareJerseyTest

@Configuration
public class SpringContextAwareJerseyTest extends JerseyTest {
    protected static String SERVLET_PATH = "/api";

    final private static ThreadLocal<ApplicationContext> context =
        new ThreadLocal<>();

    protected String getResourceLocation() {
        return "example.rest";
    }

    protected String getContextConfigLocation() {
        return getClass().getName();
    }

    static private String getContextHolderConfigLocation() {
        return SpringContextAwareJerseyTest.class.getName();
    }

    protected WebAppDescriptor configure() {
        String contextConfigLocation = getContextConfigLocation() + " " +
            getContextHolderConfigLocation();

        Map<String, String> initParams = new HashMap<>();
        initParams.put("com.sun.jersey.config.property.packages",
                       getResourceLocation());
        initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");

        return new WebAppDescriptor.Builder(initParams)
            .servletClass(SpringServlet.class)
            .contextParam(
                "contextClass",
                "org.springframework.web.context.support.AnnotationConfigWebApplicationContext")
            .contextParam("contextConfigLocation", contextConfigLocation)
            .servletPath(SERVLET_PATH)  // if not specified, it set to root resource
            .contextListenerClass(ContextLoaderListener.class)
            .requestListenerClass(RequestContextListener.class)
            .build();
    }

    protected final ApplicationContext getContext() {
        return context.get();
    }

    @Bean
    public static ContextHolder contextHolder() {
        return new ContextHolder();
    }

    private static class ContextHolder implements ApplicationContextAware {
        @Override
        public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
            context.set(applicationContext);
        }
    }
}

Using the above with jersey 1.8

Upvotes: 0

Michal Gajdos
Michal Gajdos

Reputation: 10379

One issue may be that you're trying to configure your test application in constructor and in configure() method. Use one or another but not both because in this case your configure() method is not invoked and hence you may not be using SpringServlet and everything that is defined in this method.

Upvotes: 1

Related Questions