无法模拟 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