12

我在创建用于单元测试的模拟响应对象时遇到问题。我正在使用org.glassfish.jersey.core.jersey-client2.3.1 版来实现我的 RESTful 客户端和mockito1.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;
}

请注意,我正在使用手动创建的响应来模拟 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 的任何方法。所以我的测试注定要失败:(。你们知道我能在这里做什么吗?我不想用 Mockito 模拟 Response 对象,因为我认为它可能是代码味道,因为它违反了法律得墨忒耳。真的,我在这里没有想法。这样的事情应该简单明了。

4

8 回答 8

9

您还可以使用 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))。

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

于 2016-03-03T13:01:48.870 回答
5

我有这个错误,因为当您使用 时ResponseBuilder,它返回一条OutboundJaxrsResponse无法处理的消息readEntity()

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

defaultController.get();

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

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

在您的情况下,您模拟了 simpleClient 以便回复使用ResponseBuilder未由球衣处理的响应。相当于直接调用我的DefaultController方法。

在不嘲笑该readEntity()方法的情况下,我建议您找到一种方法让 Jersey 处理您的响应并将其转换为ScopedJaxrsResponse.

希望有帮助。

于 2013-10-24T17:55:16.813 回答
4

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

我在https://codingcraftsman.wordpress.com/2018/11/26/testing-and-mocking-jersey-responses/中讨论了这个主题

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

正如其他海报所观察到的,readEntity在出站Response上会给你你放入的确切实体对象。你甚至可以将它转换为你想要的任何类型。当然,这不适用于真正的入站响应,因为入站响应只有来自服务器的入站流的文本/二进制文件。

我编写了以下代码,以允许我使用 mockito 强制出站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;
  }

  // use the getEntity from the real object as
  // the readEntity of the mock
  @SuppressWarnings("unchecked")
  private static <T> T readEntity(Response realResponse, Class<T> t) {
    return (T)realResponse.getEntity();
  }
于 2018-11-26T10:09:30.090 回答
2

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

对我来说,解决方法是将上述有问题的行替换为以下行:

String entity = (String) outboundJaxrsResponse.getEntity();
于 2016-08-05T21:32:03.923 回答
2

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

OutputClass entity = (OutputClass)outboundJaxrsResponse.entity
于 2015-09-17T20:35:49.123 回答
1

我解决了嘲笑回复:

@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,这些会有所帮助:

于 2015-12-22T03:22:17.293 回答
0

测试代码:

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

嘲讽:

doReturn("test").when(response).getEntity(any(GenericType.class));
于 2018-04-05T14:55:28.543 回答
0

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

new MockUp<OutboundJaxrsResponse>(){
        @Mock
        public String readEntity(Class<String> clazz){ return jsonValue;}
    };
于 2021-04-19T22:23:41.577 回答