A SOAP Web Service

Filed under: BDD, Cucumber, Test automation, — Tags: CXF, Cucumber-jvm, JSF, JUnit, Jersey, MVC, Model view controller, REST, RESTAssured, RESTFul, Selenium, Soap, Swing,, Swinggui, WebDriver, Wicket — Thomas Sundberg — 2012-11-01

Previous - A RESTFul web service

A SOAP Web Service is yet another way to use the model. It doesn't have any user interface and is expected to be used by third party suppliers. I divide the project using two Maven modules as earlier. One for the production code and one for the verification. The only large difference here is the support class that will connect to the system under test but now has to be adapted for a SOAP web service instead of any graphical interface or RESTFul web service. Another difference is of course that there is no GUI and that it is developed using a CXF instead, but that has little impact on the verification.

The feature is the same:

src/test/resources/se/waymark/rentit/Rent.feature

Feature: Rental cars should be possible to rent to gain revenue to the rental company.

  As an owner of a car rental company
  I want to make cars available for renting
  So I can make money

  Scenario: Find and rent a car
    Given there are 18 cars available for rental
    When I rent one
    Then there will only be 17 cars available for rental

The glue code to connect the feature above with Cucumber is the same as earlier:

src/test/java/se/waymark/rentit/RunCukesIT.java

package se.waymark.rentit;

import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
public class RunCukesIT {
}

The steps are also identical:

src/test/java/se/waymark/rentit/steps/RentStepdefs.java

package se.waymark.rentit.steps;


import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

public class RentStepdefs {
    private RentACarSupport rentACarSupport = new RentACarSupport();

    @Given("^there are (\\d+) cars available for rental$")
    public void there_are_cars_available_for_rental(int availableCars) throws Throwable {
        rentACarSupport.createCars(availableCars);
    }

    @When("^I rent one$")
    public void rent_one_car() throws Throwable {
        rentACarSupport.rentACar();
    }

    @Then("^there will only be (\\d+) cars available for rental$")
    public void there_will_be_less_cars_available_for_rental(int expectedAvailableCars) throws Throwable {
        int actualAvailableCars = rentACarSupport.getAvailableNumberOfCars();
        assertThat(actualAvailableCars, is(expectedAvailableCars));
    }
}

The first interesting difference is the help class. It now deals with a SOAP web service.

src/test/java/se/waymark/rentit/steps/RentACarSupport.java

package se.waymark.rentit.steps;

import se.waymark.rentit.soap.RentCar;
import se.waymark.rentit.soap.RentCarService;

import javax.xml.ws.BindingProvider;

public class RentACarSupport {
    private RentCar rentCar;

    public RentACarSupport() {
        String protocol = "http://";
        String host = "localhost";
        int port = 8080;
        String context = "/rentit/soap/";
        String serviceEndpoint = "rentCar";
        String endpointAddress = protocol + host + ":" + port + context + serviceEndpoint;

        RentCarService service = new RentCarService();
        rentCar = service.getRentCarPort();

        BindingProvider bindingProvider = (BindingProvider) rentCar;
        bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress);
    }

    public void createCars(int availableCars) {
        rentCar.createCars(availableCars);
    }

    public void rentACar() {
        rentCar.rentCar();
    }

    public int getAvailableNumberOfCars() {
        return rentCar.getAvailableCars();
    }
}

Instead of using Selenium, FEST or RESTAssured, I now use a generated stub to connect to the service and drive the application.

This was the interesting part from a testing point of view.

To execute this, we need a Maven pom that defines the project

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.waymark</groupId>
        <artifactId>soap-ws</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>se.waymark</groupId>
    <artifactId>soap-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.12</version>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>verify</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>
                <version>1.2.2</version>
                <executions>
                    <execution>
                        <id>start-tomcat</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop-tomcat</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <container>
                        <containerId>tomcat7x</containerId>
                        <zipUrlInstaller>
                            <url>http://www.apache.org/dist/tomcat/tomcat-7/v7.0.32/bin/apache-tomcat-7.0.32.zip</url>
                        </zipUrlInstaller>
                        <output>${project.build.directory}/tomcat-logs/container.log</output>
                        <append>false</append>
                        <log>${project.build.directory}/tomcat-logs/cargo.log</log>
                    </container>
                    <configuration>
                        <type>standalone</type>
                        <home>${project.build.directory}/tomcat-home</home>
                        <properties>
                            <cargo.servlet.port>8080</cargo.servlet.port>
                            <cargo.logging>high</cargo.logging>
                        </properties>
                        <deployables>
                            <deployable>
                                <groupId>se.waymark</groupId>
                                <artifactId>soap-main</artifactId>
                                <type>war</type>
                            </deployable>
                        </deployables>
                    </configuration>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-codegen-plugin</artifactId>
                <version>2.6.2</version>
                <executions>
                    <execution>
                        <id>generate-sources</id>
                        <phase>generate-sources</phase>
                        <configuration>
                            <wsdlOptions>
                                <wsdlOption>
                                    <wsdl>${basedir}/../soap-main/target/generated/wsdl/RentCar.wsdl</wsdl>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>se.waymark</groupId>
            <artifactId>soap-main</artifactId>
            <version>1.0-SNAPSHOT</version>
            <type>war</type>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.1.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.1.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

It is similar to the web application versions. It uses Maven fail-safe plugin to execute the test and cargo to deploy the application on a Tomcat. It also uses CXF to generate a WSDL file that can be used later to generate stubs for clients that want to connect.

The SOAP web service

To be able to build the system, I need to implement the SOAP web service. This is a trivial SOAP web service and not really important but included here for completeness. I will therefore skip through it fast. All files needed are included, but I will not go through the details. There are a lot of other people out there who are better sent to describe a SOAP web service than I am.

File organisation

All files needed for this example are organised like this:

soap-ws
|-- pom.xml
|-- soap-main
|   |-- pom.xml
|   `-- src
|       |-- main
|       |   |-- java
|       |   |   `-- se
|       |   |       `-- waymark
|       |   |           `-- rentit
|       |   |               `-- soap
|       |   |                   |-- RentCar.java
|       |   |                   `-- SoapRentCar.java
|       |   `-- webapp
|       |       `-- WEB-INF
|       |           |-- cxf-servlet.xml
|       |           `-- web.xml
|       `-- test
|           `-- java
|               `-- se
|                   `-- waymark
|                       `-- rentit
|                           `-- RentCarTest.java
`-- soap-test
    |-- pom.xml
    `-- src
        `-- test
            |-- java
            |   `-- se
            |       `-- waymark
            |           `-- rentit
            |               |-- RunCukesIT.java
            |               `-- steps
            |                   |-- RentACarSupport.java
            |                   `-- RentStepdefs.java
            `-- resources
                `-- se
                    `-- waymark
                        `-- rentit
                            `-- Rent.feature

All test files has already been presented so I will not do that again. The files needed for the SOAP web service looks like this:

Parent pom

A parent pom is used to connect the two sub modules. It is defined as:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.waymark</groupId>
    <artifactId>soap-ws</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>soap-main</module>
        <module>soap-test</module>
    </modules>
</project>

Maven pom

soap-main/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>se.waymark</groupId>
        <artifactId>soap-ws</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>se.waymark</groupId>
    <artifactId>soap-main</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <build>
        <finalName>rentit</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-java2ws-plugin</artifactId>
                <version>2.6.2</version>
                <executions>
                    <execution>
                        <id>generate-wsdl</id>
                        <phase>process-classes</phase>
                        <configuration>
                            <className>se.waymark.rentit.soap.RentCar</className>
                            <genWsdl>true</genWsdl>
                        </configuration>
                        <goals>
                            <goal>java2ws</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>se.waymark.educational</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

It builds a web application and gives it the final name rentit. It also generates a wsdl file that can be used later to create clients. It turns out that the parameter genWsdl must be set to true for the wsdl to be generated. The fact that the goal is java2ws is not enough to generate the wsdl file. This surprised me somewhat.

The web service

The web service is defined in an interface like this:

src/main/java/se/waymark/rentit/soap/RentCar.java

package se.waymark.rentit.soap;

import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public interface RentCar {
    void createCars(@WebParam(name = "initialNumberOfCars") Integer initialNumberOfCars);

    void rentCar();

    Integer getAvailableCars();
}

The implementation is then done as:

src/main/java/se/waymark/rentit/soap/SoapRentCar.java

package se.waymark.rentit.soap;

import se.waymark.rentit.model.dao.CarDAO;
import se.waymark.rentit.model.dao.InMemoryCarDAO;
import se.waymark.rentit.model.entiy.Car;

import javax.jws.WebService;

@WebService(endpointInterface = "se.waymark.rentit.soap.RentCar",
        serviceName = "RentCarService")
public class SoapRentCar implements RentCar {
    private static CarDAO carDAO = new InMemoryCarDAO();

    @Override
    public void createCars(Integer numberOfCars) {
        for (int i = 0; i < numberOfCars; i++) {
            Car car = new Car();
            carDAO.add(car);
        }
    }

    @Override
    public void rentCar() {
        Car car = carDAO.findAvailableCar();
        car.rent();
    }

    @Override
    public Integer getAvailableCars() {
        return carDAO.getNumberOfAvailableCars();
    }
}

Web application

The service is wired in:

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <display-name>Soap Web App</display-name>
    <servlet>
        <servlet-name>cxf</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cxf</servlet-name>
        <url-pattern>/soap/*</url-pattern>
    </servlet-mapping>
</web-app>

The service class is then defined in:

src/main/webapp/WEB-INF/cxf-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://cxf.apache.org/jaxws
                           http://cxf.apache.org/schemas/jaxws.xsd">
    <jaxws:server id="jaxwsService" serviceClass="se.waymark.rentit.soap.RentCar" address="/rentCar">
        <jaxws:serviceBean>
            <bean class="se.waymark.rentit.soap.SoapRentCar"/>
        </jaxws:serviceBean>
    </jaxws:server>
</beans>

Unit test

I wired the application together using this test:

src/test/java/se/waymark/rentit/RentCarTest.java

package se.waymark.rentit;

import org.junit.Test;
import se.waymark.rentit.soap.RentCar;
import se.waymark.rentit.soap.SoapRentCar;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

public class RentCarTest {

    @Test
    public void shouldRentACar() {
        int initialNumberOfCars = 43;

        RentCar rentCar = new SoapRentCar();
        rentCar.createCars(initialNumberOfCars);

        rentCar.rentCar();

        int oneRentedCar = 1;
        Integer expected = initialNumberOfCars - oneRentedCar;

        Integer actual = rentCar.getAvailableCars();

        assertThat(actual, is(expected));
    }

}

Conclusion

A simple SOAP web service can be added on top of the model without changing the defined behaviour. This is Behaviour Driven Development. You define the behaviour you want. Implement it. Add an interface if it is needed but don't change the behaviour if you don't have to.

Next - Conclusion



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
Executable specification
Gradle
Guice
J2EE
JUnit
Java
Linux
Load testing
Maven
Mockito
Pair programming
PicoContainer
Programming
Public speaking
Quality
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS