SoapUI has this nifty little Maven plugin that is capable of running mock services during Maven's test phase. This enables us to write integration tests that do real network I/O and marshall/unmarshall real XML requests and responses. Not only do we get to exercise more of our integration code during tests but it's also easier - is there anything worse than mocking complex response patterns in code?
This post walks through the highlights to set up a Maven integration test project that utilize the SoapUI Maven plugin. The full project can be cloned from GitHub, linked in the last section.
In a real enterprise, services often have big and complex responses that can exercise different code paths in the service consumer depending on the data in the response. For our service mock to be of any real value, it has to match different request payloads on the same service operation to different responses, so that we get to test all possible code paths. Although the service we will use for this project is quite simple, we will demonstrate SoapUI's QUERY-MATCH feature, that enable us to return different responses from the same service operation.
The Maven integration project will consist of two modules, one named "ServiceResources" that generate the JAX-WS service client artifacts from a WSDL file, and another named "ServiceClient" that contain the actual integration test, which is run with JUnit and the soapui-maven-plugin.
The following WSDL file is used to generate the JAX-WS service client and the SoapUI mock service. It describes a simple Document/Literal service with a single operation called SendMessage, that sends a String message and returns a String response.
The file is stored in the Maven project under ServiceResources/src/main/resources/test.wsdl
A typical request for this service would look like this:
And a typical response:
Wsimport is a tool bundled with the JDK that can be used to create a Java service client from a WSDL file. We are not using the tool directly, instead we use the "wsimport" goal of the jaxws-maven-plugin to create the client in Maven's "generate-sources" phase.
What we end up with is a JAX-WS Service and Port class and a number of JAXB classes that are used at runtime to marshall and unmarshall between the XML messages and their Java class representation.
The jaxws-maven-plugin is configured in the pom.xml file of the ServiceResources module. We dont' have to configure the plugin a whole lot but here's a few points:
Start SoapUI 5.4.0 and select File->New SOAP Project. Point "Initial WSDL" to ServiceResources/src/test/resources/test.wsdl. Make sure to check "Relative Paths".
Once the project is created right-click the project and select New SOAP Mockservice. Name it "testmock".
Right-click the new test mock and select New MockOperation and then select the SendMessage operation from the dropdown.
We are going to create two mock responses for the SendMessage operation. One that returns the response message "triggered" when the mock service receives a request with the message "hello" and another that returns the response message "Default". The latter will be returned on any other request message than "hello".
Since SoapUI already created a "Response 1" for the SendMessage operation, we will just customize that response a bit. Right-click "Response 1" and select Rename. Name it "Triggered response". Change the response XML body to return the response string "triggered". It should look like this:
Create another mock response by right-clicking the SendMessage operation and selecting "New MockResponse". Name it "Default response" and change its response XML body to return the response string "Default":
Now double-click the SendMessage operation under the test mock.
In the Default Response dropdown menu select the response we named "Default response".
In the "dispatch" dropdown menu select QUERY_MATCH. Click the green plus sign to add a new query match. Name it "message". Make sure "message" is selected, then add this content to the XPATH field:
Set the "Expected value to "hello" and in the "Dispatch to" dropdown menu select the "Triggered response".
Close the SendMessage popup and and press ctrl-alt-s to save the project. Store the SoapUI project under ServiceClient/src/test/soapui/test-soapui-project.xml. It's important that the SoapUI project file is stored in the same Maven module as the JUnit test cases (we will get to them later).
The configuration of the soapui-maven-plugin is added to the pom.xml of the Maven module that also hosts the JUnit test cases for the integration test. That would be the ServiceClient module in our case, but for at layered application, split across multiple Maven modules, you would probably launch your integration tests from a higher layer such as the UI layer.
To configure the plugin, point the parameter "projectFile" to the SoapUI project XML file that we stored under ServiceClient/src/test/soapui/test-soapui-project.xml. The mock service is obviously launched on localhost, but the port and path are overridden, so that we control them, rather than using whatever was stored in the SoapUI project's XML file.
The plugin's "mock" goal is bound to Maven's "process-test-classes" phase. This is the phase just before the actual "test" phase.
Notice that the port is set to the property ${mockport}. We use a plugin called port-allocator-maven-plugin to dynamically assign a free port number to ${mockport}. Otherwise we could have build jobs, running on a build server such as Jenkins, trying to bind the mock service to the same port at the same time.
The only configuration needed for port-allocator-maven-plugin is to name the property for the port number:
The dynamic port number assigned by the port-allocator-maven-plugin must also be available to the test code. This is done by writing the Maven build properties to a file on the classpath using the properties-maven-plugin:
The JUnit test simply reads the properties from classpath to get the mockport. Then it builds the endpoint address, calls the mock service using the generated Service, Port and JAXB classes and finally asserts that the response is as expected.
As mentioned previously, there are two test cases to demonstrate, that SoapUI can match a request to a particular mock reponse based on the content of the request. One test case calls the service with the message "hello" to test the "triggered" response and another calls with another message to test the default response.
The test class is stored under ServiceClient/src/test/java/ServiceTest.java
The Maven project is available on GitHub: https://github.com/SorenPoulsen/mock-services-with-maven-soapui-plugin
These are the requirements to run the integration test:
Clone the GIT project and run Maven from the root folder of the project:
$ git clone https://github.com/SorenPoulsen/mock-services-with-maven-soapui-plugin.git
$ cd mock-services-with-maven-soapui-plugin/ServiceMock
$ mvn test