Unable to test Jax-rs with JSON entity

2019-08-09 22:36发布

问题:

I am trying to test a Jax-rs resource by following this https://jersey.java.net/documentation/latest/test-framework.html,
and I am using container jersey-test-framework-provider-jdk-http

I can assert status code. However, when I try to readEntity, I get exception:

javax.ws.rs.ProcessingException: Unable to find a MessageBodyReader of content-type application/json and type class java.lang.String
    at org.jboss.resteasy.core.interception.ClientReaderInterceptorContext.throwReaderNotFound(ClientReaderInterceptorContext.java:39)
    at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.getReader(AbstractReaderInterceptorContext.java:73)
    at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.proceed(AbstractReaderInterceptorContext.java:50)
    at org.jboss.resteasy.client.jaxrs.internal.ClientResponse.readFrom(ClientResponse.java:248)
    at org.jboss.resteasy.client.jaxrs.internal.ClientResponse.readEntity(ClientResponse.java:181)
    at org.jboss.resteasy.specimpl.BuiltResponse.readEntity(BuiltResponse.java:217)

My Resource Class:

@Path("/")
public class SampleResource {
    @GET
    @Path("/health")
    @Produces(MediaType.APPLICATION_JSON)
    public String getServiceStatus() {
        return "{\"Status\": \"OK\"}";
    }    
}

My Test Class:

public class TestSampleResource extends JerseyTest {
    @Override
    protected Application configure() {
        return new ResourceConfig(SampleResource.class);
    }

    @Test
    public void testHealthEndpoint() {            
        Response healthResponse = target("health").request(MediaType.APPLICATION_JSON).get();

        Assert.assertEquals(200, healthResponse.getstatus());  // works

        String body = healthResponse.readEntity(String.class);
        Assert.assertEquals("{\"Status\": \"OK\"}", body);
    }        
}

Can anyone please help?

回答1:

The problem comes from having both Jersey and RestEasy client on the classpath. When you call target() on the JerseyTest, the WebTarget is obtained from a Client that is built by calling ClientBuilder.newClient().

The ClientBuilder is a standard JAX-RS API, and it is implemented first to search for an implementation of ClientBuilder through the META-INF/services files, looking for a file named javax.ws.rs.client.ClientBuilder, whose content is the name of an implementation of the ClientBuilder. If no such file is found, it defaults to looking for JerseyClientBuilder.

jersey-client has no such file META-INF/services/javax.ws.rs.core.ClientBuilder because it's ClientBuilder is the default for JAX-RS client. If you look in your resteasy-client jar, you will see the it does have that file. And if you look in the contents of that file, you will see the ResteasyClientBuilder as the implementation.

So even though you are using Jersey's test framework, the Client being used, is RESTeasy's implementation. And I guess all the standard configurations with entity providers never gets configured. Conversion between String and application/json is one of those standard providers you need in your case.

I would say just explicitly use Jersey client implementation. You will no longer be able to call target on the JerseyTest. You will need to explicitly create the client

@Test
public void dotest() {
    final Client client = new JerseyClientBuilder().build();
    WebTarget target = client.target("http://localhost:9998");
    final Response response = target.path("health").request().get();
    final String json = response.readEntity(String.class);
}

The default base path for JerseyTest is http://localhost:9998, so I explicitly create the WebTarget with that.

Note that I said the String to/from application/json is supported by standard providers. So if you will only be serializing Strings, then you don't need anything else. If you want POJO serialization support for the Jersey client (and server side), you should add the following

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey2.version}</version>
    <scope>test</scope>
</dependency>


回答2:

I suspect the json parser in your test is being misguided by the presence of curly braces. Basically it thinks you are returning a json object, not a json string. Try returning "Status:OK"



回答3:

As the exception says you are missing a MessageBodyReader for content-type application/json. Do you have JacksonJsonProvider on your classpath? It can be added as a dependency to jackson-jaxrs-json-provider:

<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.7.3</version>
</dependency>

Then register the JacksonJsonProvider in your test application:

@Override
protected Application configure() {
    return new ResourceConfig(SampleResource.class, JacksonJsonProvider.class);
}