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

Posted

技术标签:

【中文标题】无法模拟 Glassfish Jersey 客户端响应对象【英文标题】:Unable to Mock Glassfish Jersey Client response object 【发布时间】:2013-11-02 16:08:55 【问题描述】:

我在创建用于单元测试的模拟 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;

请注意,我正在使用手动创建的响应来模拟 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 对象,因为我认为它可能是代码味道,因为它违反了法律得墨忒耳。真的,我在这里没有想法。这样的事情应该简单明了。

【问题讨论】:

【参考方案1】:

我遇到了这个错误,因为当您使用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

希望对您有所帮助。

【讨论】:

托马斯,感谢您的意见!!只有一个问题……如果是你……你会模拟 readEntity() 方法吗? 这取决于你想测试什么? getAll 方法做了它应该做的事情,并根据它收到的内容返回它应该返回的内容。【参考方案2】:

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

OutputClass entity = (OutputClass)outboundJaxrsResponse.entity

【讨论】:

不得不更改源代码并不理想,但这成功了。谢谢。【参考方案3】:

我解决了嘲笑回复的问题:

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

http://www.javacodegeeks.com/2013/08/jersey-client-testing-external-calls.html http://www.smipple.net/snippet/somatik/Jersey%20REST%20client%20mocking

【讨论】:

【参考方案4】:

您还可以使用 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 并附加了一些自定义实体。

【讨论】:

这确实应该是答案【参考方案5】:

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

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

String entity = (String) outboundJaxrsResponse.getEntity();

【讨论】:

或者LinkedHashMap result = (LinkedHashMap)successResponse.getEntity(); .. 谢谢,这比接受的回复更有帮助!【参考方案6】:

测试代码:

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

嘲讽:

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

【讨论】:

【参考方案7】:

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

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

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

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

我编写了以下代码,以允许我使用 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();
  

【讨论】:

readEntity(toReturn, type) 方法从何而来?使用 asOutbound.getEntity() 代替对我有用,但我很好奇为什么我不能让你的方法按原样工作。 对于那些发现这有点死胡同的人感到抱歉 - 我忘记提供 readEntity 函数。我已经更新了帖子以显示它。【参考方案8】:

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

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

【讨论】:

以上是关于无法模拟 Glassfish Jersey 客户端响应对象的主要内容,如果未能解决你的问题,请参考以下文章

java.lang.NoSuchMethodError:org.glassfish.jersey.server.ApplicationHandler。

org.glassfish.jersey.servlet.ServletContainer ClassNotFoundException

实例化 servlet 类 org.glassfish.jersey.servlet.ServletContainer 时出错

org.glassfish.jersey.server.ContainerException:调用服务时的 java.lang.ExceptionInInitializerError

引起:java.lang.NoSuchMethodError: org.glassfish.jersey.message.internal.HeaderUtils.asStringHeadersSin

org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException:找不到媒体类型的 MessageBodyWrite