Integration tests with CXF client
One convenient possibility to write integration tests is to use the proxy-based CXF client. It makes it possible to reuse interfaces on the client side.
The complete source code is available here.
Technologies used :
- Java SE Development Kit 8u66
- Eclipse IDE for Java Developers Version: Mars.1 Release (4.5.1)
- Maven 3.3.3 (comes with Eclipse)
- Spring Framework 4.2.3 (as Maven dependency)
- Apache CXF 3.1.4 (as Maven dependency)
- JUnit 4.12
pom.xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-client</artifactId>
<version>3.1.4</version>
<scope>test</scope>
</dependency>
Test Client Spring Configuration
We will use dependency injection to set our test client. We need to specify the interface and to register a JSON provider for serialization / deserialization.
<jaxrs:client id="testClient" address="http://localhost:8080/rest"
serviceClass="de.griesser.rest.services.PersonService">
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
</jaxrs:providers>
</jaxrs:client>
Test Class
Here is the test scenario :
- Get all the resources. Initially this should return an empty list.
- Create a resource.
- Get all the resources again. This should return a list containing one element : the resource we created previously.
- Get a single resource by its id. This should return the resource we created in step 2.
- Delete the resource. To leave the application in the state it was before the tests.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:test-client.xml")
@TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class })
public class PersonServiceTest {
private static final String LAST_NAME = "Griesser";
private static final String FIRST_NAME = "Nadege";
@Autowired
private PersonService sut;
@Test
public void test() throws PersonNotFound {
testInitialGetAll();
String id = testCreate();
testGetAllAfterCreate(id);
testGetAfterCreate(id);
testDelete(id);
}
protected void testInitialGetAll() {
// given
// when
Collection<PersonResource> res = sut.getAll();
// then
Assert.assertNotNull(res);
Assert.assertTrue(res.isEmpty());
}
protected String testCreate() {
// given
PersonResource person = new PersonResource();
person.setFirstName(FIRST_NAME);
person.setLastName(LAST_NAME);
// when
person = sut.create(person);
// then
Assert.assertNotNull(person);
Assert.assertNotNull(person.getId());
Assert.assertEquals(FIRST_NAME, person.getFirstName());
Assert.assertEquals(LAST_NAME, person.getLastName());
return person.getId();
}
protected void testGetAllAfterCreate(String id) {
// given
// when
Collection<PersonResource> res = sut.getAll();
// then
Assert.assertNotNull(res);
Assert.assertEquals(1, res.size());
PersonResource person = res.iterator().next();
checkPerson(person, id);
}
protected void testGetAfterCreate(String id) throws PersonNotFound {
// given
// when
PersonResource person = sut.get(id);
// then
checkPerson(person, id);
}
protected void testDelete(String id) throws PersonNotFound {
// given
// when
sut.delete(id);
// then
}
protected void checkPerson(PersonResource person, String id) {
Assert.assertNotNull(person);
Assert.assertEquals(id, person.getId());
Assert.assertEquals(FIRST_NAME, person.getFirstName());
Assert.assertEquals(LAST_NAME, person.getLastName());
}
}
Test Automatisation
This is done in the pom.xml.
We will slightly modify the configuration of the Jetty plugin to start Jetty as a daemon in the pre-integration-test phase and stop it in the post-integration-test phase.
The Surefire plugin will be used to compile and execute the tests. We want these tests to be executed during the integration-test phase and not the test phase. We will use package inclusion / exclusion for that, ignore all tests in the integration package during the test phase (we currently have no other tests, but we will in the future) and consider only the tests in this package during the integration test phase.
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.3.6.v20151106</version>
<configuration>
<stopPort>11079</stopPort>
<stopKey>stop</stopKey>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<scanintervalseconds>0</scanintervalseconds>
<daemon>true</daemon>
</configuration>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<executions>
<execution>
<id>default-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<excludes>
<exclude>**/integration/*.java</exclude>
</excludes>
</configuration>
</execution>
<execution>
<id>integration-test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<includes>
<include>**/integration/*.java</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Launch
mvn clean install
Output
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building rest 2.2.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ rest ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ rest ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ rest ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ rest ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.19:test (default-test) @ rest ---
[INFO]
[INFO] --- maven-war-plugin:2.2:war (default-war) @ rest ---
[INFO] Packaging webapp
[INFO] Assembling webapp [rest] in [C:\Users\ngriesser\git\code-samples\rest\target\rest]
[INFO] Processing war project
[INFO] Copying webapp resources [C:\Users\ngriesser\git\code-samples\rest\src\main\webapp]
[INFO] Webapp assembled in [207 msecs]
[INFO] Building war: C:\Users\ngriesser\git\code-samples\rest\target\rest.war
[INFO] WEB-INF\web.xml already added, skipping
[INFO]
[INFO] >>> jetty-maven-plugin:9.3.6.v20151106:start (start-jetty) > validate @ rest >>>
[INFO]
[INFO] <<< jetty-maven-plugin:9.3.6.v20151106:start (start-jetty) < validate @ rest <<<
[INFO]
[INFO] --- jetty-maven-plugin:9.3.6.v20151106:start (start-jetty) @ rest ---
[INFO] Configuring Jetty for project: rest
[INFO] webAppSourceDirectory not set. Trying src\main\webapp
[INFO] Reload Mechanic: automatic
[INFO] Classes = C:\Users\ngriesser\git\code-samples\rest\target\classes
[INFO] Logging initialized @4875ms
[INFO] Context path = /
[INFO] Tmp directory = C:\Users\ngriesser\git\code-samples\rest\target\tmp
[INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] web.xml file = file:///C:/Users/ngriesser/git/code-samples/rest/src/main/webapp/WEB-INF/web.xml
[INFO] Webapp directory = C:\Users\ngriesser\git\code-samples\rest\src\main\webapp
[INFO] jetty-9.3.6.v20151106
[INFO] No Spring WebApplicationInitializer types detected on classpath
[INFO] Initializing Spring root WebApplicationContext
2015-12-13 08:36:56 INFO ContextLoader - Root WebApplicationContext: initialization started
2015-12-13 08:36:56 INFO XmlWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Sun Dec 13 08:36:56 CET 2015]; root of context hierarchy
2015-12-13 08:36:56 INFO XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/beans.xml]
Dez 13, 2015 8:36:57 AM org.apache.cxf.endpoint.ServerImpl initDestination
INFORMATION: Setting the server's publish address to be /rest
2015-12-13 08:36:57 INFO ContextLoader - Root WebApplicationContext: initialization completed in 1032 ms
[INFO] Started o.e.j.m.p.JettyWebAppContext@6869a3b3{/,file:///C:/Users/ngriesser/git/code-samples/rest/src/main/webapp/,AVAILABLE}{file:///C:/Users/ngriesser/git/code-samples/rest/src/main/webapp/}
[INFO] Started ServerConnector@25ffd826{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
[INFO] Started @7900ms
[INFO] Started Jetty Server
[INFO]
[INFO] --- maven-surefire-plugin:2.19:test (integration-test) @ rest ---
-------------------------------------------------------
T E S T S
-------------------------------------------------------
2015-12-13 08:36:58 INFO DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@42e26948]
Running de.griesser.rest.services.integration.PersonServiceTest
2015-12-13 08:36:58 INFO DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@589838eb]
2015-12-13 08:36:58 INFO XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [test-client.xml]
2015-12-13 08:36:58 INFO GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@2ac273d3: startup date [Sun Dec 13 08:36:58 CET 2015]; root of context hierarchy
2015-12-13 08:36:59 INFO PersonServiceImpl - getAll()
2015-12-13 08:36:59 INFO PersonServiceImpl - getAll() returned [] in 0 msecs
2015-12-13 08:36:59 INFO PersonServiceImpl - create(PersonResource [id=null, lastName=Griesser, firstName=Nadege])
2015-12-13 08:36:59 INFO PersonServiceImpl - create(PersonResource [id=null, lastName=Griesser, firstName=Nadege]) returned PersonResource [id=b2036497-ce3b-4144-ad8b-1df0118d46b7, lastName=Griesser, firstName=Nadege] in 1 msecs
2015-12-13 08:36:59 INFO PersonServiceImpl - getAll()
2015-12-13 08:36:59 INFO PersonServiceImpl - getAll() returned [PersonResource [id=b2036497-ce3b-4144-ad8b-1df0118d46b7, lastName=Griesser, firstName=Nadege]] in 0 msecs
2015-12-13 08:36:59 INFO PersonServiceImpl - get(b2036497-ce3b-4144-ad8b-1df0118d46b7)
2015-12-13 08:36:59 INFO PersonServiceImpl - get(b2036497-ce3b-4144-ad8b-1df0118d46b7) returned PersonResource [id=b2036497-ce3b-4144-ad8b-1df0118d46b7, lastName=Griesser, firstName=Nadege] in 0 msecs
2015-12-13 08:36:59 INFO PersonServiceImpl - delete(b2036497-ce3b-4144-ad8b-1df0118d46b7)
2015-12-13 08:36:59 INFO PersonServiceImpl - delete(b2036497-ce3b-4144-ad8b-1df0118d46b7) returned null in 0 msecs
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.431 sec - in de.griesser.rest.services.integration.PersonServiceTest
2015-12-13 08:36:59 INFO GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@2ac273d3: startup date [Sun Dec 13 08:36:58 CET 2015]; root of context hierarchy
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- jetty-maven-plugin:9.3.6.v20151106:stop (stop-jetty) @ rest ---
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ rest ---
[INFO] Stopped ServerConnector@25ffd826{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
[INFO] Installing C:\Users\ngriesser\git\code-samples\rest\target\rest.war to C:\Users\ngriesser\.m2\repository\de\griesser\rest\2.2.0-SNAPSHOT\rest-2.2.0-SNAPSHOT.war
[INFO] Installing C:\Users\ngriesser\git\code-samples\rest\pom.xml to C:\Users\ngriesser\.m2\repository\de\griesser\rest\2.2.0-SNAPSHOT\rest-2.2.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.621 s
[INFO] Finished at: 2015-12-13T08:37:00+01:00
[INFO] Final Memory: 23M/439M
[INFO] ------------------------------------------------------------------------