Book Image

SoapUI Cookbook

By : Rupert Anderson
Book Image

SoapUI Cookbook

By: Rupert Anderson

Overview of this book

Table of Contents (19 chapters)
SoapUI Cookbook
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

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:

  1. Create a SoapUI project from invoice_v1.wadl. Go to File Menu | New REST Project | Import WADL, browse to invoice_v1.wadl, and click on OK. This should generate a project with a sample request to the invoice resource that takes an id path parameter, that is, http://localhost:9000/invoiceservice/v1/invoice/{id}.

  2. Next, create a simple TestSuite, TestCase, and TestStep operations with Assertion to specify what we expect back from a successful invoice resource request. We can use the Generate TestSuite option to do this:

    1. Right-click on invoice_v1 Endpoint and select Generate TestSuite.

    2. Change the style to Single TestCase with one Request for each Method and click on OK.

    3. Accept the suggested name as invoice_v1 TestSuite.

    4. The project should then contain TestSuite with one generated TestStep operation for invoice/{id}.

  3. Now, we're ready to add some Assertions to the TestStep. 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
    }}
  4. 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
  5. 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
  6. 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.

  7. 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 of wadl2java only generates the client code from the WADL and not the service code like we need.

  8. 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 for invoice_v1.wadl by running wadl2java. 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 the CLASSPATH variable with export 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
      
  9. 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
    
  10. 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…
    
  11. 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.

  12. Now, it's time to run TestCase that we created in step 2:

    • Open the TestCase and edit the TestStep created in step 2.

    • Add an invoice ID to the TestSteps's request, for example, 12345.

    • Running the TestCase should result in all the TestStep'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.

  13. 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.

  14. 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:

  1. 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.

  2. 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 the Invoice.java class by calling the createInvoice() factory method. There is also a factory method JAXBElement<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 the getInvoiceid(…) method passing in the {id} path parameter as the String 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 the Invoice getInvoiceId(String id) of this class so that if the invoice (id) is 12345, then we a create a new Invoice object using the ObjectFactory, 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 the Invoice 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