junit 强制Jersey读取来自JerseyTest的模拟

w41d8nur  于 2022-11-11  发布在  其他
关注(0)|答案(4)|浏览(85)

我想使用JerseyTest测试资源。我已创建了以下测试:

@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");
    }
}

我的资源也有一个带有@Autowired注解的AObject引用。
我的问题是我的JerseyTestResource(由测试配置的)有不同的Mock对象示例。在控制台中,我看到testApplicationContext.xml被加载了两次,一次用于测试,一次用于资源。
我怎么能强迫泽使用同样的嘲弄呢?

fzwojiic

fzwojiic1#

在调试jersey-spring 3(版本2.9.1)库之后,问题似乎出在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;
}

它检查应用程序属性中是否存在名为“contextConfig”的属性,如果不存在,它将初始化spring应用程序上下文。即使您在测试中初始化了spring应用程序上下文,jersey也会创建另一个上下文并使用该上下文。因此,我们必须以某种方式将测试中的ApplicationContext传递到Jersey Application类中。解决方案如下:

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

上面的类将从我们的测试中获取应用程序上下文,并将其传递给已配置的ResourceConfig,以便SpringComponentProvider将相同的应用程序上下文返回给jersey。我们还使用jersey-spring-applicationContext.xml,以便包含jersey特定的Spring配置。

  • 我们无法从JerseyTest继承,因为它会在初始化测试应用程序内容之前,先在建构函式中初始化Application。*

现在,您可以使用此基类创建测试,例如

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

在testContext.xml中添加以下定义,以便注入模拟AObject。

<bean class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.yourcompany.AObject" />
</bean>
pdkcd3nj

pdkcd3nj2#

我无法从@Grigoris的工作中得到答案https://stackoverflow.com/a/24512682/156477,尽管他对为什么会发生这种情况的解释是正确的。
最后,我选择了下面的方法,它公开了一个特殊的setter来插入mock对象。虽然不像上面的方法那样“干净”,但值得一试,它公开了我想模拟的apiProvider,这样我就可以编写一些测试了。

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("....)            
    }
}
gfttwv5a

gfttwv5a3#

如果有人对Kevin为Jersey v1提供的https://stackoverflow.com/a/40591082/4894900解决方案感兴趣:

public MyAPITest extends JerseyTest {

    @InjectMocks
    MyAPI myAPI;

    @Mock
    MyApiService myApiService;

    @Override
    protected AppDescriptorconfigure()
    {  
        MockitoAnnotations.initMocks(this);

        ResourceConfig rc = new DefaultResourceConfig();
        rc.getSingletons().add(myAPI);

        return new LowLevelAppDescriptor.Builder(rc).contextPath("context").build();
    }

    @Test
    public void test() {
        // I can now define the mock behaviours
        when(myApiService...)

        WebResource webResource = resource().path("mypath");
        ClientResponse result = webResource.get(ClientResponse.class);            
    }
}
ekqde3dh

ekqde3dh4#

通过删除xml依赖性进一步改进可接受的解决方案。更多详细信息here

泽西Spring测试抽象泽西测试

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

/**Run JerseyTest with custom spring context with mocks Mimics Spring @WebMvcTest with @MockBean */
public abstract class JerseySpringTest extends JerseyTest {

  @Override
  protected ResourceConfig configure() {
    MockitoAnnotations.openMocks(this);
    enable(TestProperties.LOG_TRAFFIC);
    enable(TestProperties.DUMP_ENTITY);
    set(TestProperties.CONTAINER_PORT, "0");
    final ResourceConfig resourceConfig =
        new ResourceConfig()
            .property("contextConfig", createSpringContext(getBeanMap()))
            .property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, "WARNING")
            .register(getResourceClass());
    return serverConfig(resourceConfig);
  }

  /**
   * Gives the test class opportunity to further customize the configuration. Like registering a
   * MultiPartFeature if required.
   *
   * @param config
   * @return
   */
  protected ResourceConfig serverConfig(final ResourceConfig config) {
    return config;
  }

  /**
   * Supplies all the bean objects required to be loaded in the application context for the Resource class
   * under test
   *
   * @return
   */
  protected List<Object> getBeans() {
    return Collections.emptyList();
  }

   /**
   * Supplies all the bean objects with name qualifier required to be loaded in the application context for the Resource class
   * under test
   *
   * @return
   */
  protected Map<String, Object> getQualifiedBeans() {
    return Collections.emptyMap();
  }

  private Map<String, Object> getBeanMap() {
    final Map<String, Object> result = new HashMap<>();
    CollectionUtils.emptyIfNull(getBeans())
        .forEach(obj -> result.put(StringUtils.uncapitalize(obj.getClass().getSimpleName()), obj));
    result.putAll(MapUtils.emptyIfNull(getQualifiedBeans()));
    return result;
  }

  /**
   * Resource class under test
   *
   * @return
   */
  protected abstract Class<?> getResourceClass();

  /**
   * Creates & returns a Spring GenericApplicationContext from the given beans with qualified names
   *
   * @param beans
   * @return
   */
  public static ApplicationContext createSpringContext(Map<String, Object> beans) {
    final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    MapUtils.emptyIfNull(beans).forEach((k, obj) -> beanFactory.registerSingleton(k, obj));
    final GenericApplicationContext context = new GenericApplicationContext(beanFactory);
    context.refresh();
    return context;
  }
}

包含测试的示例资源:

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import lombok.RequiredArgsConstructor;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Path("/rest")
@Component
@RequiredArgsConstructor
class RestResource {
  private final ServiceXYZ serviceXYZ;
  private final ServiceABC serviceABC;
  @Qualifier("greeter")
  private final String greeterName;

  @GET
  @Path("/serviceXYZ/greet/{name}")
  public Response greetByServiceXYZ(@PathParam("name") final String name) {
    return Response.ok(serviceXYZ.greet(name) + ", Regards: " + greeterName).build();
  }

  @GET
  @Path("/serviceABC/greet/{name}")
  public Response greetByServiceABC(@PathParam("name") final String name) {
    return Response.ok(serviceABC.greet(name)+ ", Regards: " + greeterName).build();
  }
}

@Service
class ServiceXYZ {
  public final String greet(final String name) {
    return "Welcome " + name + " to Hello World!";
  }
}

@Service
class ServiceABC {
  public final String greet(final String name) {
    return "Welcome " + name + " to Hello Universe!";
  }
}

class ResourceTest extends JerseySpringTest {

  @InjectMocks private RestResource subject;
  @Mock private ServiceXYZ serviceXYZ;
  @Mock private ServiceABC serviceABC;

  // only required to override for custom server config, say if the Resource accepts file input
  @Override
  protected ResourceConfig serverConfig(final ResourceConfig config) {
    return config.register(MultiPartFeature.class);
  }

  @Override
  protected Map<String, Object> getQualifiedBeans() {
    return Map.of("greeter", "Amith Kumar");
  }

  @Override
  protected List<Object> getBeans() {
    return List.of(subject, serviceXYZ, serviceABC);
  }

  @Override
  protected Class<?> getResourceClass() {
    return RestResource.class;
  }

  // only required to override for custom client config, say if the Resource accepts file input
  @Override
  protected void configureClient(ClientConfig config) {
    config.register(MultiPartFeature.class);
  }

  @Test
  void testServiceXYZGreets() {
    // ARRANGE
    when(serviceXYZ.greet("foo")).thenReturn("Hello foo");

    // ACT
    Response output = target("/rest/serviceXYZ/greet/foo").request().get();

    // ASSERT
    assertAll(
        () -> assertEquals(Status.OK.getStatusCode(), output.getStatus()),
        () -> assertEquals("Hello foo, Regards: Amith Kumar", output.readEntity(String.class)));
  }

  @Test
  void testServiceABCGreets() {
    // ARRANGE
    when(serviceXYZ.greet("boo")).thenReturn("Hola boo");

    // ACT
    Response output = target("/rest/serviceABC/greet/boo").request().get();

    // ASSERT
    assertAll(
        () -> assertEquals(Status.OK.getStatusCode(), output.getStatus()),
        () -> assertEquals("Hola boo, Regards: Amith Kumar", output.readEntity(String.class)));
  }
}

相关问题