无法模拟Glassfish Jersey客户端响应对象

时间:2013-10-24 05:28:34

标签: unit-testing glassfish mockito jersey-client jersey-2.0

我在创建用于单元测试的模拟Response对象时遇到问题。我使用org.glassfish.jersey.core.jersey-client版本2.3.1来实现我的RESTful客户端和mockito版本1.9.5来帮助我使用模拟对象。这是我的测试代码:

@Test
public void testGetAll() throws IOException {
    // Given
    String expectedResource = "expectedResource"

    final Response expectedRes =  Response.ok(expectedResource, MediaType.APPLICATION_JSON).build();
    String receivedResource;

    BDDMockito.given(this.client.getSimpleClient().getAllWithResponse()).willReturn(expectedRes);

    // When
    receivedResource = this.client.getAll();

    // Then
    Assert.assertNotNull("Request constructed correctly and response received.", receivedResource);
    Assert.assertEquals("Resource is equal to expected.", expectedResource, receivedResource);
}

执行this.client.getAll();时会出现问题。这是该方法的代码:

public String getAll() throws GenericAragornException, ProcessingException{
    Response response = this.simpleClient.getAllWithResponse();

    if (response.getStatus() != 200) {
        processErrorResponse(response);
    }

    String entity = response.readEntity(String.class);

    // No errors so return entity converted to resourceType.
    return entity;
}

请注意,我正在使用手动创建的Response来模拟this.simpleClient.getAllWithResponse()方法。当它到达response.readEntity(resourceListType);指令时,Jersey会抛出以下异常:java.lang.IllegalStateException - Method not supported on an outbound message.。经过大量的研究和调试后,由于某种原因,当我使用响应构建器(例如Response.ok(expectedResource, MediaType.APPLICATION_JSON).build();)创建响应时,它会将其创建为OutboundResponse而不是 InboundResponse 。后者是唯一允许使用Response.readEntity()方法的人。如果是 OutboundResponse ,则抛出异常。

但是,我找不到任何方法将手动创建的响应转换为InboundResponse。所以我的测试注定要失败:(。你们这些人/ gals是否知道我能在这里做什么?我不想用Mockito模仿Response对象因为我认为这可能是代码味道,因为它违反了法律规定德米特。我真诚地想到这里。这样的事情应该简单明了。

8 个答案:

答案 0 :(得分:5)

我遇到此错误,因为当您使用ResponseBuilder时,它会返回无法使用OutboundJaxrsResponse处理的readEntity()消息。

我注意到只有当我直接调用Jax-RS组件时才会出现此错误。例如,如果我DefaultController注明@Path("/default")并且我尝试直接调用其方法,则无法使用readEntity()并且出现与您相同的错误。

defaultController.get();

现在,当我使用grizzly2测试提供程序并使用客户端定位Rest Url时(在前一种情况下,它是/default),我在响应中收到的消息是ScopedJaxrsResponse。然后我可以使用readEntity()方法。

target("/default").request.get();

在您的情况下,您模拟了simpleClient,以便使用未由jersey处理的ResponseBuilder构建的响应进行回复。它与直接调用DefaultController方法相当。

在不嘲笑readEntity()方法的情况下,我建议您找一种方法让泽西处理您的回复并转换为ScopedJaxrsResponse

希望有所帮助。

答案 1 :(得分:3)

您还可以使用Mockito模拟回复:

final Response response = Mockito.mock(Response.class);
Mockito.when(response.getStatus()).thenReturn(responseStatus);
Mockito.when(response.readEntity(Mockito.any(Class.class))).thenReturn(responseEntity);
Mockito.when(response.readEntity(Mockito.any(GenericType.class))).thenReturn(responseEntity);

responseStatus是与响应关联的状态代码,responseEnitty当然是您要返回的实体。您可以将该模拟用作Mockito中的返回语句(例如... thenReturn(response))。

在我的项目中,我为模拟创建了不同类型的构建器(在这种情况下为Response),因此我可以轻松地按需构建所需的模拟,例如:响应状态代码200和附加的某个自定义实体。

答案 2 :(得分:2)

不是使用readEntity(OutputClass.class),而是可以执行以下操作:

OutputClass entity = (OutputClass)outboundJaxrsResponse.entity

答案 3 :(得分:2)

对我来说,这些答案都不起作用,因为我试图编写一个服务器端单元测试来测试生成的pandas.dateframe实例的主体本身。我通过调用像Response这样的行来获得此异常。而不是模仿String entity = readEntity(String.class)对象,我想测试它。

对我而言,解决方法是将上述有问题的行替换为:

Response

答案 4 :(得分:0)

我解决了嘲笑回应:

@Spy Response response;

...

// for assignments in code under testing:
doReturn( response ).when( dwConnector ).getResource( anyString() );

// for entity and response status reading:
doReturn( "{a:1}".getBytes() ).when( response ).readEntity( byte[].class );
doReturn( 200 ).when( response ).getStatus();

如果您的操舵手动创建ScopedJaxrsResponse,这些可以提供帮助:

答案 5 :(得分:0)

测试代码:

ClientResponse<?> response = response.getEntity(new GenericType<List<String>>() {});

嘲讽:

doReturn("test").when(response).getEntity(any(GenericType.class));

答案 6 :(得分:0)

我自己遇到了这个问题,尝试使用Response.ok(entity).build()将客户端模拟为远程服务,然后允许我的客户端代码对来自伪造服务器的响应进行response.readEntity(...)

我在https://codingcraftsman.wordpress.com/2018/11/26/testing-and-mocking-jersey-responses/

中讨论了这个主题

问题在于Response.build()方法旨在产生 outbound 响应,该响应旨在在被真实客户端接收并反序列化之前进行序列化。 readEntity方法有望在反序列化时被调用。

正如其他张贴者所观察到的那样,出站readEntity上的Response将为您提供您放入的确切实体对象。您甚至可以将其转换为所需的任何类型。当然,这对于真正的入站响应不起作用,因为入站响应仅包含来自服务器的入站流的文本/二进制文件。

我编写了以下代码,以允许我使用模仿者来强制将出站Response伪装成入站代码:

  /**
   * Turn a locally made {@link Response} into one which can be used as though inbound.
   * This enables readEntity to be used on what should be an outbound response
   * outbound responses don't support readEntity, but we can fudge it using
   * mockito.
   * @param asOutbound response built as though being sent to the received
   * @return a spy on the response which adds reading as though inbound
   */
  public static Response simulateInbound(Response asOutbound) {

    Response toReturn = spy(asOutbound);
    doAnswer(answer((Class<?> type) -> readEntity(toReturn, type)))
        .when(toReturn)
        .readEntity(ArgumentMatchers.<Class<?>>any());
    return toReturn;
  }

答案 7 :(得分:0)

只需使用您需要返回的任何内容来模拟 OutboundJaxrsResponse#readEntity() 方法。例如(使用 JMockit):

new MockUp<OutboundJaxrsResponse>(){
        @Mock
        public String readEntity(Class<String> clazz){ return jsonValue;}
    };