簡體   English   中英

強制 Jersey 從 JerseyTest 讀取模擬

[英]Force Jersey to read mocks from JerseyTest

我想用 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 (由測試配置)具有不同的模擬 object 實例。 在控制台中,我看到testApplicationContext.xml加載了兩次,一次用於測試,一次用於資源。

如何強制 jersey 使用相同的模擬?

調試jersey-spring3(版本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也會創建另一個上下文並使用該上下文。 所以我們必須以某種方式從Jersey Application類中的測試中傳遞ApplicationContext。 解決方案如下:

@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來包含特定於針織物的彈簧配置。

我們不能從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>

我無法從@Grigoris工作中得到答案https://stackoverflow.com/a/24512682/156477 ,盡管他對其原因的解釋是正確的。

最后,我采用下面的方法,公開了一個特殊的setter來插入模擬對象。 不像上面的方法那樣“干凈”,但值得權衡暴露我想要模擬的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("....)            
    }
}

如果有人對Kevin for 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);            
    }
}

通過刪除 xml 依賴關系,進一步改進了可接受的解決方案。 此處提供更多詳細信息。

JerseySpringTest 抽象 JerseyTest

import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
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(getBeans()))
            .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 beans required to be loaded in the application context for the Resource class
   * under test
   *
   * @return
   */
  protected abstract List<Object> getBeans();

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

  /**
   * Creates & returns a Spring GenericApplicationContext from the given beans
   *
   * @param beans
   * @return
   */
  private ApplicationContext createSpringContext(List<Object> beans) {
    final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    CollectionUtils.emptyIfNull(beans)
        .forEach(
            obj ->
                beanFactory.registerSingleton(
                    StringUtils.uncapitalize(obj.getClass().getSimpleName()), 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;

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

  @GET
  @Path("/serviceABC/greet/{name}")
  public Response greetByServiceABC(@PathParam("name") final String name) {
    return Response.ok(serviceABC.greet(name)).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 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", 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", output.readEntity(String.class)));
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM