测试 JAX-RS Web 服务? ?

我目前正在寻找为基于 JAX-RS 的 web 服务创建自动化测试的方法。

我基本上需要一种方法来发送某些输入,并验证我得到预期的响应。我更愿意通过 JUnit 来完成这项工作,但我不确定如何实现这一点。

您使用什么方法来测试您的 Web 服务?

更新: 正如 entzik 所指出的,将 Web 服务与业务逻辑分离使我能够对业务逻辑进行单元测试。但是,我也想测试正确的 HTTP 状态码等。

85168 次浏览

You probably wrote some java code that implements your business logic and then you have generated the web services end point for it.

An important thing to do is to independently test your business logic. Since it's pure java code you can do that with regular JUnit tests.

Now, since the web services part is just an end point, what you want to make sure is that the generated plumbing (stubs, etc) are in sync with your java code. you can do that by writing JUnit tests that invoke the generated web service java clients. This will let you know when you change your java signatures without updating the web services stuff.

If your web services plumbing is automatically generated by your build system at every build, then it may not be necessary to test the end points (assuming it's all properly generated). Depends on your level of paranoia.

I use Apache's HTTPClient (http://hc.apache.org/) to call Restful Services. The HTTP Client library allows you to easily perform get, post or whatever other operation you need. If your service uses JAXB for xml binding, you can create a JAXBContext to serialize and deserialize inputs and outputs from the HTTP request.

Jersey comes with a great RESTful client API that makes writing unit tests really easy. See the unit tests in the examples that ship with Jersey. We use this approach to test the REST support in Apache Camel, if you are interested the test cases are here

Though its too late from the date of posting the question, thought this might be useful for others who have a similar question. Jersey comes with a test framework called the Jersey Test Framework which allows you to test your RESTful Web Service, including the response status codes. You can use it to run your tests on lightweight containers like Grizzly, HTTPServer and/or EmbeddedGlassFish. Also, the framework could be used to run your tests on a regular web container like GlassFish or Tomcat.

An important thing to do is to independently test your business logic

I certainly would not assume that the person who wrote the JAX-RS code and is looking to unit test the interface is somehow, for some bizarre, inexplicable reason, oblivious to the notion that he or she can unit testing other parts of the program, including business logic classes. It's hardly helpful to state the obvious and the point was repeatedly made that the responses need to be tested, too.

Both Jersey and RESTEasy have client applications and in the case of RESTEasy you can use the same annoations (even factor out annotated interface and use on the client and server side of your tests).

REST not what this service can do for you; REST what you can do for this service.

You can try out REST Assured which makes it very simple to test REST services and validating the response in Java (using JUnit or TestNG).

As James said; There is built-in test framework for Jersey. A simple hello world example can be like this:

pom.xml for maven integration. When you run mvn test. Frameworks start a grizzly container. You can use jetty or tomcat via changing dependencies.

...
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.16</version>
</dependency>


<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>2.16</version>
<scope>test</scope>
</dependency>


<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.16</version>
<scope>test</scope>
</dependency>
</dependencies>
...

ExampleApp.java

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;


@ApplicationPath("/")
public class ExampleApp extends Application {


}

HelloWorld.java

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;


@Path("/")
public final class HelloWorld {


@GET
@Path("/hello")
@Produces(MediaType.TEXT_PLAIN)
public String sayHelloWorld() {


return "Hello World!";
}
}

HelloWorldTest.java

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.core.Application;
import static org.junit.Assert.assertEquals;


public class HelloWorldTest extends JerseyTest {


@Test
public void testSayHello() {


final String hello = target("hello").request().get(String.class);


assertEquals("Hello World!", hello);
}


@Override
protected Application configure() {


return new ResourceConfig(HelloWorld.class);
}
}

You can check this sample application.

Take a look at Alchemy rest client generator. This can generate a proxy implementation for your JAX-RS webservice class using jersey client behind the scene. Effectively you will call you webservice methods as simple java methods from your unit tests. Handles http authentication as well.

There is no code generation involved if you need to simply run tests so it is convenient.

Dislclaimer: I am the author of this library.

As I understand the main purpose of the auther of this issue is to decouple JAX RS layer from business one. And unit test only the first one. Two basic problems here we have to resolve:

  1. Run in test some web/application server, put JAX RS components in it. And only them.
  2. Mock business services inside JAX RS components/REST layer.

The first one is solved with Arquillian. The second one is perfectly described in arquillican and mock

Here is an example of the code, it may differ if you use another application server, but I hope you'll get the basic idea and advantages.

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;


import com.brandmaker.skinning.service.SomeBean;


/**
* Created by alexandr on 31.07.15.
*/
@Path("/entities")
public class RestBean
{
@Inject
SomeBean bean;


@GET
public String getEntiry()
{
return bean.methodToBeMoked();
}
}


import java.util.Set;


import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;


import com.google.common.collect.Sets;


/**
*/
@ApplicationPath("res")
public class JAXRSConfiguration extends Application
{
@Override
public Set<Class<?>> getClasses()
{
return Sets.newHashSet(RestBean.class);
}
}




public class SomeBean
{
public String methodToBeMoked()
{
return "Original";
}
}


import javax.enterprise.inject.Specializes;


import com.brandmaker.skinning.service.SomeBean;


/**
*/
@Specializes
public class SomeBeanMock extends SomeBean
{
@Override
public String methodToBeMoked()
{
return "Mocked";
}
}


@RunWith(Arquillian.class)
public class RestBeanTest
{
@Deployment
public static WebArchive createDeployment() {
WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(JAXRSConfiguration.class, RestBean.class, SomeBean.class, SomeBeanMock.class)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
System.out.println(war.toString(true));
return war;
}


@Test
public void should_create_greeting() {
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://127.0.0.1:8181/test/res/entities");
//Building the request i.e a GET request to the RESTful Webservice defined
//by the URI in the WebTarget instance.
Invocation invocation = target.request().buildGet();
//Invoking the request to the RESTful API and capturing the Response.
Response response = invocation.invoke();
//As we know that this RESTful Webserivce returns the XML data which can be unmarshalled
//into the instance of Books by using JAXB.
Assert.assertEquals("Mocked", response.readEntity(String.class));
}
}

A couple of notes:

  1. JAX RS configuration without web.xml is used here.
  2. JAX RS Client is used here (no RESTEasy/Jersey, they expose more convenient API)
  3. When test starts, Arquillian's runner starts working. Here you can find how to configure tests for Arquillian with needed application server.
  4. Depending on the chosen application server, an url in the test will differ a little bit. Another port may be used. 8181 is used by Glassfish Embedded in my example.

Hope, it'll help.

Keep it simple. Have a look at https://github.com/valid4j/http-matchers which can be imported from Maven Central.

    <dependency>
<groupId>org.valid4j</groupId>
<artifactId>http-matchers</artifactId>
<version>1.0</version>
</dependency>

Usage example:

// Statically import the library entry point:
import static org.valid4j.matchers.http.HttpResponseMatchers.*;


// Invoke your web service using plain JAX-RS. E.g:
Client client = ClientBuilder.newClient();
Response response = client.target("http://example.org/hello").request("text/plain").get();


// Verify the response
assertThat(response, hasStatus(Status.OK));
assertThat(response, hasHeader("Content-Encoding", equalTo("gzip")));
assertThat(response, hasEntity(equalTo("content")));
// etc...