【问题标题】:Unable to Mock Glassfish Jersey Client response object无法模拟 Glassfish Jersey 客户端响应对象
【发布时间】: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 对象,因为我认为它可能是代码味道,因为它违反了法律得墨忒耳。真的,我在这里没有想法。这样的事情应该简单明了。

【问题讨论】:

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


    【解决方案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,这些会有所帮助:

    【讨论】:

      【解决方案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;}
            };
        

        【讨论】:

          猜你喜欢
          • 2018-08-03
          • 1970-01-01
          • 1970-01-01
          • 2016-10-02
          • 2012-03-26
          • 1970-01-01
          • 2013-05-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多