如何在内存中进行单元测试 Spring-Jersey

Posted

技术标签:

【中文标题】如何在内存中进行单元测试 Spring-Jersey【英文标题】:How to in-memory unit test Spring-Jersey 【发布时间】:2015-04-21 07:08:42 【问题描述】:

我正在使用 Spring-Jersey3,但无法弄清楚如何使用 Spring bean 对 RESTFul API 进行单元测试

控制器

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());
    

服务接口

package com.service;

public interface DataSource 
    public String load();

服务实现

package com.service;

import org.springframework.stereotype.Repository;

@Repository
public class DataSourceImpl implements DataSource 

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

ResourceRegister.java(球衣资源寄存器)

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(应用程序上下文)

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

单元测试

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());
    

项目Source Code

问题:依赖注入返回 null

【问题讨论】:

【参考方案1】:

我要解决的一些问题:

您正在使用 Jersey 1.x 风格的 Jersey 测试框架,但您的应用是 Jersey 2.x。请参阅下面的 2.x 依赖项。

我从未使用过 Jersey 1.x 风格的测试框架,但在 Jersey 2.x 中,In-Memory container 不支持依赖于 servlet 的功能。不同的依赖见下文。

使用 Jersey 测试框架,您无需自己创建 Client。已经创建了一个,我们可以简单地调用JerseyTesttarget(String path)方法来取回一个WebTarget(Jersey 2.x,WebResource是Jersey 1.x)

这是一个有效的重构。

依赖项(我只添加了这个依赖项,并没有取出任何东西,因为你的 GitHub 项目没有包含任何与测试相关的东西,就像你上面的代码示例那样)

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

测试

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);
      

对于那些不使用 xml 上下文文件的,你可以使用一个注释配置应用程序上下文,并将其添加为一个 init 参数

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

其他资源:

Jersey Test Framework Documentation More examples from Test Framework source code tests。 (提示:我提供的链接是针对 grizzly Web 容器示例的,但如果您返回到 providers,您可以查看每个提供程序并转到测试包以获取这些提供程序的示例)

更新

所以经过几次测试后,我发现了一些有趣的事情

一个:

有了上面的依赖,即使我们不配置DeploymentContext,只是覆盖JerseyTest中的Application configure(),它仍然可以工作。无法真正解释它,但似乎描述符仍然被拾取。

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);
      

二:

即使我们摆脱了上述依赖(grizzly)并使用内存中的依赖,同样简单的之前的测试仍然有效。文档说明

In-Memory 容器不是真正的容器。它启动 Jersey 应用程序并直接调用内部 API 来处理由测试框架提供的客户端创建的请求。不涉及网络通信。此容器不支持 servlet 和其他容器相关功能,但它是简单单元测试的完美选择。

所以我不完全确定他们指的是什么 Servlet 功能,因为这个测试仍然有效

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

我特别不明白的,是这个说法

“不涉及网络通信”

因为当我运行测试时,我看到了一个日志

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

【讨论】:

所以我试图找出这个被覆盖的方法来自于 DeploymentContext configureDeployment()。我已经碰到了几个版本的 JerseyTest 框架,但找不到这个方法的来源 我会摆脱所有与 Jersey 相关的依赖项,这些依赖项不是 org.glassfish.jersey。我猜你还在使用旧的JerseyTest,它没有那个方法。 我同意 - 这个测试框架在幕后做了一些疯狂的魔法

以上是关于如何在内存中进行单元测试 Spring-Jersey的主要内容,如果未能解决你的问题,请参考以下文章

使用带有事务的内存数据库进行单元测试时,如何抑制 InMemoryEventId.TransactionIgnoredWarning?

你还应该在单元测试中进行内存管理吗? (OCUnit)

您将如何对内存分配器进行单元测试?

如何在订阅Angular7单元测试用例中对代码进行单元测试

使用内存磁盘进行 I/O 单元测试

再谈EF Core内存数据库单元测试问题