Generating and developing a RESTful web service stub test-first
This recipe shows how to generate and develop a simple RESTful web service stub test-first using TDD. The main SoapUI learning will be how to test a simple RESTful web service defined by a WADL that produces JSON responses. Basic JAX-RS web service development skills using Apache CXF can also be learned here.
Getting ready
The example service is a REST version of the SOAP invoice service from the first recipe. The service is defined by a WADL with the following main properties:
- WADL
: invoice_v1.wadl
- Service endpoint:
http://localhost:9000/invoiceservice/v1
- Resource:
GET /invoice/{id}
- Produces:
application/json
Apache CXF will be used to generate, build, and run the stub web service. See the Getting ready section in the first recipe if you need advice on how to download Apache CXF.
Tip
Eclipse users
If you are using Eclipse, you can set up Apache CXF as a runtime library that is by navigating to Project | Add Library | CXF Runtime, and run the server class as a Java application.
The invoice-v1-soapui-project.xml
project for this recipe can be found in the this chapter's sample code files.
How to do it...
First, we'll create a REST project from the service's WADL, and add a TestStep
with Assertions
to check whether the response's invoice values are what we expect. Then, we'll generate an empty runnable REST web service using Apache CXF, and finally add a simple implementation to pass the test. Perform the following steps:
- Create a SoapUI project from
invoice_v1.wadl
. Go to File Menu | New REST Project | Import WADL, browse toinvoice_v1.wadl
, and click on OK. This should generate a project with a sample request to the invoice resource that takes anid
path parameter, that is,http://localhost:9000/invoiceservice/v1/invoice/{id}
. - Next, create a simple
TestSuite
,TestCase
, andTestStep
operations withAssertion
to specify what we expect back from a successful invoice resource request. We can use the Generate TestSuite option to do this:- Right-click on
invoice_v1 Endpoint
and select Generate TestSuite. - Change the style to Single TestCase with one Request for each Method and click on OK.
- Accept the suggested name as
invoice_v1 TestSuite
. - The project should then contain
TestSuite
with one generatedTestStep
operation forinvoice/{id}
.
- Right-click on
- Now, we're ready to add some
Assertions
to theTestStep
. Say we're expecting a JSON representation of an Invoice document that will look like the following:{"Invoice": { "id": 12345, "companyName": "Test Company", "amount": 100 }}
- Then, if you've got SoapUI Pro, we can use 3
JsonPath Match Assertions
:Name: IdShouldBe12345 JsonPath: $.Invoice.id expectedValue: 12345 Name: AmountShouldBe100 JsonPath: $.Invoice.amount Expected Value: 100 Name: CompanyNameShouldBeTestCompany JsonPath: $.Invoice.companyName Expected Value: Test Company
- For open source SoapUI, we can add 3
Contains Assertions
:Name: ShouldContainText12345 Contains Content: 12345 Name: ShouldContainTextTestCompany Contains Content: Test Company Name: ShouldContainText100 Contains Content: 100
- In both versions of SoapUI we can check whether the HTTP status is 200 OK by adding a
Valid HTTP Status Codes
Assertion
:Name: ShouldReturnHTTPStatus200 HTTP Status Code = 200
Tip
Want to also check JSONSchema Compliance?
See the Testing REST response JSON schema compliance recipe of Chapter 4, Web Service Test Scenarios, for how to do it.
- Now that our tests are ready, we're going to need to generate the actual service. We can do this using Apache CXF's
wadl2java
script to generate the Java service types and empty the implementation from the WADL.Note
SoapUI's WADL2Java menu option is not what it seems
Unfortunately, in the current version (5.0) of SoapUI, the WADL2Java functionality (http://www.soapui.org/REST-Testing/rest-code-generation.html) is written to use classic
wadl2java
(https://wadl.java.net/). This version ofwadl2java
only generates the client code from the WADL and not the service code like we need. - Of course, generating web service code directly using Apache CXF is not part of SoapUI. I have included these steps for completeness and in case you find them useful. If you would rather skip this part, I have included the generated code in
<chapter 1 samples>/rest/invoicev1_gen
. Otherwise, you can generate the web service code forinvoice_v1.wadl
by runningwadl2java.
For example:cd <apache-cxf-3.0.1 home>/ ./bin/wadl2java -d <chapter1 samples>/rest/invoicev1/src/main/java/ -p rest.invoice.v1 -impl -interface <chapter1 samples>/rest/invoicev1/wadl/invoice_v1.wadl
Tip
Classpath Issue on MacOSX/Linux
When running
wadl2java
with Apache CXF 3.01, if you see this error:Could not find or load main class org.apache.cxf.tools.wadlto.WADLToJava
, then manually setting theCLASSPATH
variable withexport
CLASSPATH
=apache-cxf-3.0.1/lib/*
fixes the problem.- You should see the following output:
Aug 18, 2014 8:57:07 PM org.apache.cxf.common.jaxb.JAXBUtils logGeneratedClassNames INFO: Created classes: generated.Invoice, generated.ObjectFactory
- The following Java source files generated at the location set by the
–d
parameter and–p
gives the package structure:rest/invoice/v1/InvoiceserviceV1Resource rest/invoice/v1/InvoiceserviceV1ResourceImpl rest/invoice/v1/Invoice rest/invoice/v1/ObjectFactory rest/invoice/v1/Service
- You should see the following output:
- Next, we need to compile the generated service. Note that Apache CXF's libraries are required on the classpath (the
-cp
parameter):cd <chapter1 samples>/rest/invoicev1/src/main/java/rest/invoice/v1/ javac -cp "<apache-cxf-3.0.1 home>/lib/*" -d <chapter1 samples>/rest/invoicev1/target/classes/ *.java
- Execute the following command to run the server:
cd <chapter1 samples>/rest/invoicev1/target/classes/ java -cp "<apache-cxf-3.0.1 home>/lib/*:." rest.invoice.v1.Server … INFO logging… … Server ready…
- Give the server a quick test by browsing to
http://localhost:9000/invoiceservice/v1?_wadl
, and you should see a WADL that indicates that the server is running. - Now, it's time to run
TestCase
that we created in step 2:- Open the
TestCase
and edit theTestStep
created in step 2. - Add an invoice ID to the
TestSteps's
request, for example, 12345. - Running the
TestCase
should result in all theTestStep's
Assertions
failing, and a response with HTTP status 204 no content under the Raw tab. This is expected since we have no implementation yet.
- Open the
- Now that we have a failing test, we are ready to implement the invoice resource:
- First implement
InvoiceserviceV1ResourceImpl.java
with the following code:package rest.invoice.v1; public class InvoiceserviceV1ResourceImpl implements InvoiceserviceV1Resource { public Invoice getInvoiceid(String id) { ObjectFactory objectFactory = new ObjectFactory(); Invoice invoice = objectFactory.createInvoice(); if (id != null && id.equals("12345")) { invoice.setId("12345"); invoice.setCompanyName("Test Company"); invoice.setAmount(100.0d); } return invoice; } }
Note
Skip the dev?
A completed version of the code can be found at
<chapter1 samples>/rest/invoicev1_impl
. - Next, add the annotation
@XmlRootElement(name = "Invoice")
; otherwise, marshaling from the JavaBean to the response JSON doesn't work:@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "invoice", propOrder = { "id", "companyName", "amount" }) @XmlRootElement(name = "Invoice") public class Invoice { …
- Add an import statement for the annotation to the top of
Invoice.java
:import javax.xml.bind.annotation.XmlRootElement;
- Finally, delete the
package-info.java
class; otherwise, there will be a namespace prefix on the JSON response.
- First implement
- Next, recompile and restart the server as described in steps 9 and 10. Then, rerunning the
TestCase
should pass!
How it works...
Let's take a look at the main solution points:
- The web service we create uses the JAX-RS standard, which is the official Java standard for RESTful web services (see https://jax-rs-spec.java.net/). One key difference with JAX-WS seen in the first recipe is that the JDK does not ship with a JAX-RS implementation; only the JAX-RS interfaces and annotations are supplied. So, we instead use the Apache CXF JAX-RS implementation; hence, we need to supply the Apache CXF libraries at compile and runtime.
- Apache CXF generated the following Java classes using the WADL definition:
Invoice.java
: This is a JavaBean representation of the invoice XML content. This class has binding annotations to allow the Apache CXF JAX-RS implementation to marshal invoice objects to XML content and unmarshal XML content to invoice objects:@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "invoice", propOrder = { "id", "companyName", "amount" }) @XmlRootElement(name = "Invoice")
Tip
To understand more about these binding annotations the technology to look at is Java Architecture for XML Binding (JAXB)—see https://jaxb.java.net/tutorial/.
ObjectFactory.java
: This class can optionally be used to create instances of theInvoice.java
class by calling thecreateInvoice()
factory method. There is also a factory methodJAXBElement<Invoice> createInvoice(Invoice value)
to create JAXB invoice XML bindings. These factory methods can be useful to separate object creation code from your service methods when dealing with more complicated schema examples, but they are not especially useful in our case.InvoiceserviceV1Resource.java
:This is a JAX-RS annotated Java interface to represent the RESTful invoice service and its resource. In this example, we have the following code:@Path("/invoiceservice/v1/") public interface InvoiceserviceV1Resource { @GET @Produces("application/json") @Path("/invoice/{id}") Invoice getInvoiceid(@PathParam("id") String id); }
- The annotations are used by the Apache CXF JAX-RS implementation to map HTTP requests to matching Java methods. In this case, implementations of this interface that is
InvoiceserviceV1ResourceImpl
will invoke thegetInvoiceid(…)
method passing in the {id} path parameter as theString id
variable if there is a HTTP GET request to the resource/invoiceservice/v1/invoice/{id}
. Other annotated service methods to support POST, PUT and DELETE requests could also be added here and in the implementation. See<chapter 1 samples>/rest/invoice_crud/src/main/java/rest/invoice/crud/v1/InvoiceServiceCRUDV1Resource.java
for an example like this. InvoiceserviceV1ResourceImpl.java
: This is the implementation of the preceding interface to provide the Java code to run when a matching request is made. We added code to theInvoice getInvoiceId(String id)
of this class so that if the invoice (id
) is 12345, then we a create a newInvoice
object using theObjectFactory
, populate it with the expected values, and return it in the response. In the background, Apache CXF is able to marshal this into JSON content before dispatching the response back to SoapUI. Unlike the JAX-WS example in the first recipe, there was no holder object, so we were responsible for creating theInvoice
object ourselves.Service.java
: This is a server class that publishes our stub service's implementation. Like in the first recipe's JAX-WS server code, the endpoint and service timeout can be set here.
There's more...
Apart from using WADLs to create SoapUI projects for RESTful web services, there are also SoapUI plugins to use more modern alternatives such as RAML(http://raml.org/) and Swagger (http://swagger.io/) definitions as well—see Chapter 10, Using Plugins for more information.
Code-first REST services
RESTful web services will often be developed code-first and may not present a WADL or a structured definition to generate your SoapUI project and tests from. In these cases, you? can easily build your REST project by manually entering the service's URI, resources, methods, and parameters using their respective menu options, see http://www.soapui.org/getting-started/rest-testing.html. Or if you're a pro version user, you can use SoapUI to generate your project and tests by recording your requests to the service's API (see the next recipe). If you're an open source user, then you can also generate tests in a similar way by using the HTTP Monitor (See http://www.soapui.org/HTTP-Recording/concept.html).
See also
- For more information on WADL, go to https://wadl.java.net/
- For more information on Apache CXF JAX-RS, go to http://cxf.apache.org/docs/jax-rs.html