Integration testing with Cargo, Rest-assured, and Maven failsafe

Integration Tests are as important as unit tests. They validate a feature from a higher level, and involve more components or layers at the same time. Integration tests (IT tests) are given more importance when an environment needs to evolve fast. Design processes often require iterations, and unit tests sometimes seriously impact our ability to refactor, while higher level testing is less impacted comparatively.

Getting ready

This recipe shows how to develop automated IT tests that focus on Spring MVC web services. Such IT Tests are not behavioral tests as they don't assess the user interface at all. To test behaviors, an even higher testing level would be necessary, simulating the User journey through the application interface.

We will configure the Cargo Maven Plugin to stand up a test environment as part of the pre-integration-test Maven phase. On the integration-test phase, we will get the Maven failsafe plugin to execute our IT Tests. Those IT Tests will make use of the REST-assured library to run HTTP requests against the test environment and assert the HTTP responses.

How to do it…

  1. We have designed Integration tests in the cloudstreetmarket-api module. These tests are intended to test the API controller methods.
    How to do it…
  2. The great Rest-assured library comes with the following Maven dependency:
      <dependency>
        <groupId>com.jayway.restassured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>2.7.0</version>
      </dependency>
  3. A typical example of an IT Test using REST-assured would be the following UserControllerIT.createUserBasicAuth():
    public class UserControllerIT extends AbstractCommonTestUser{
      private static User userA;
      @Before
      public void before(){
        userA = new User.Builder()
          .withId(generateUserName())
          .withEmail(generateEmail())
          .withCurrency(SupportedCurrency.USD)
          .withPassword(generatePassword())
          .withLanguage(SupportedLanguage.EN)
          .withProfileImg(DEFAULT_IMG_PATH)
          .build();
      }
      @Test
      public void createUserBasicAuth(){
        Response responseCreateUser = given()
          .contentType("application/json;charset=UTF-8")
          .accept("application/json"")
          .body(userA)
          .expect
          .when()
          .post(getHost() + CONTEXT_PATH + "/users");
      String location = 
          responseCreateUser.getHeader("Location");
      assertNotNull(location);
      Response responseGetUser = given()
          .expect().log().ifError()
          .statusCode(HttpStatus.SC_OK)
          .when()
          .get(getHost() + CONTEXT_PATH + location + 
          		JSON_SUFFIX);
        UserDTO userADTO = 
          deserialize(responseGetUser.getBody().asString());
        assertEquals(userA.getId(), userADTO.getId());
        assertEquals(userA.getLanguage().name(), 
        userADTO.getLanguage());
        assertEquals(HIDDEN_FIELD, userADTO.getEmail());
        assertEquals(HIDDEN_FIELD, userADTO.getPassword());
        assertNull(userA.getBalance());
      }
    }
  4. Because they take longer to execute, we wanted to decouple the IT Tests execution from the main Maven life cycle. We have associated those IT Tests to a Maven profile named integration.

    Note

    Maven profiles offer the possibility to optionally enrich a Maven build with extra life cycle bindings. For instance, our integration profile is activated passing this profile id as Profile argument in the usual command:

    $ mvn clean install -P integration

  5. For our API IT tests, we have located the profile-specific configuration in the cloudstreetmarket-api pom.xml file:
    <profiles>
      <profile>
      <id>integration</id>
      <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>2.12.4</version>
          <configuration>
          <includes>
            <include>**/*IT.java</include>
          </includes>
          <excludes>
            <exclude>**/*Test.java</exclude>
          </excludes>
       </configuration>
       <executions>
          <execution>
            <id>integration-test</id>
            <goals>
              <goal>integration-test</goal>
            </goals>
          </execution>
          <execution>
            <id>verify</id>
            <goals><goal>verify</goal></goals>
          </execution>
       </executions>
     </plugin>
     <plugin>
      <groupId>org.codehaus.cargo</groupId>
      <artifactId>cargo-maven2-plugin</artifactId>
      <version>1.4.16</version>
          <configuration>
          <wait>false</wait>
          <container>
          <containerId>tomcat8x</containerId>
                <home>${CATALINA_HOME}</home>
          <logLevel>warn</logLevel>
          </container>
          <deployer/>
          <type>existing</type>
          <deployables>
          <deployable>
          <groupId>edu.zc.csm</groupId>
          <artifactId>cloudstreetmarket-api</artifactId>
          <type>war</type>
            <properties>
              <context>api</context>
            </properties>
          </deployable>
          </deployables>
        </configuration>
        <executions>
          <execution>
            <id>start-container</id>
            <phase>pre-integration-test</phase>
            <goals>
             <goal>start</goal>
             <goal>deploy</goal>
          </goals>
        </execution>
        <execution>
          <id>stop-container</id>
          <phase>post-integration-test</phase>
          <goals>
             <goal>undeploy</goal>
             <goal>stop</goal>
          </goals>
             </execution>
          </executions>
        </plugin>
      </plugins>
      </build>
      </profile>
    </profiles>
  6. Before attempting to run them on your machine, check that you have a CATALINA_HOME environment variable pointing to your Tomcat directory. If not, you must create it. The variable to set should be the following (if you have followed Chapter 1, Setup Routine for an Enterprise Spring Application):
    • C: omcat8: on MS Windows
    • /home/usr/{system.username}/tomcat8: on Linux
    • /Users/{system.username}/tomcat8: on Mac OS X
  7. Also, ensure that Apache HTTP, Redis, and MySQL are up and running on your local machine (see previous chapter if you have skipped it).
  8. When ready:
    • either execute the following Maven command in your Terminal (if you have the Maven directory in your path):
                 mvn clean verify -P integration
      
    • or create a shortcut for this custom build in your Eclipse IDE from the Run | Run Configurations… menu. The Build configuration to create is the following:
    How to do it…
  9. Running this command (or shortcut) should:
    1. deploy the api.war to the local Tomcat Server
    2. start the local Tomcat
    3. execute the test classes matching the **/*IT.java pattern

    If all the tests pass, you should see the [INFO] BUILD SUCCESS message.

  10. In between, when the build comes to the API, you should see the following bit of stack trace suggesting the successful execution of our IT tests:
    How to do it…

How it works...

We will explain in this section why we have introduced the Maven failsafe plugin, how the Cargo Plugin configuration satisfies our needs, how we have used REST-assured, and how useful this REST-assured library is.

Maven Failsafe versus Maven Surefire

We are using Maven failsafe to run Integration tests and Maven Surefire for unit tests. This is a standard way of using these plugins. The following table reflects this point, with the Plugins' default naming patterns for test classes:

 

Maven Surefire

Maven Failsafe

Default tests inclusion patterns

**/Test*.java
**/*Test.java
**/*TestCase.java

**/IT*.java
**/*IT.java
**/*ITCase.java

Default output directory

${basedir}/target/surefire-reports

${basedir}/target/failsafe-reports

Bound to build phase

test

pre-integration-test
integration-test
post-integration-test
verify

For Maven Failsafe, you can see that our overridden pattern inclusion/exclusion was optional. About the binding to Maven build phases, we have chosen to trigger the execution of our integration tests on the integration-test and verify phases.

Code Cargo

Cargo is a lightweight library that offers standard API for operating several supported containers (Servlet and JEE containers). Examples of covered API operations are artifacts' deployments, remote deployments and container start/stop. When used through Maven, Ant, or Gradle, it is mostly used for its ability to provide support to Integration Tests but can also serve other scopes.

Cargo Maven Plugin

We have used Cargo through its Maven plugin org.codehaus.cargo:cargo-maven2-plugin to automatically prepare an integration environment that we can run integration tests against. After the integration tests, we expect this environment to shut down.

Binding to Maven phases

The following executions have been declared as part of the cargo-maven2-plugin configuration:

<executions>
  <execution>
    <id>start-container</id>
    <phase>pre-integration-test</phase>
    <goals>
      <goal>start</goal>
    <goal>deploy</goal>
      </goals>
  </execution>
  <execution>
        <id>stop-container</id>
    <phase>post-integration-test</phase>
      <goals>
      <goal>undeploy</goal>
      <goal>stop</goal>
        </goals>
  </execution>
</executions>

Let's visit what happens when the mvn install command is executed.

The install is a phase of the default Maven life cycle. As explained in Chapter 1, Setup Routine for an Enterprise Spring Application , the default life cycle has 23 build phases from validate to deploy. The install phase is the 22nd, so 22 phases are checked to see whether there are plugin goals that could be attached to them.

Here, the pre-integration-test phase (that appears in the default life cycle between validate and install) will trigger the processes that are located under the start and deploy goals of our maven Cargo plugin. It is the same logic with post-integration-test triggers the undeploy and stop goals.

Before the IT tests execution, we start and deploy the Tomcat server. These IT tests are processed with Maven failsafe in the integration-test phase. Finally, the Tomcat server is undeployed and stopped.

IT Tests can also be executed with the verify phase (if the server is started out of the default Maven life cycle).

Using an existing Tomcat instance

In the Cargo Maven plugin configuration, we are targeting an existing instance of Tomcat. Our application is currently depending upon MySQL, Redis, Apache HTTP, and a custom session management. We have decided that the IT Tests execution will be required to be run in a proper integration environment.

Without all these dependencies, we would have got Cargo to download a Tomcat 8 instance.

Rest assured

REST-assured is an open source library licensed Apache v2 and supported by the company Jayway. It is written with Groovy and allows making HTTP requests and validating JSON or XML responses through its unique functional DSL that drastically simplify the tests of REST services.

Static imports

To effectively use REST-assured, the documentation recommends adding static imports of the following packages:

  • com.jayway.restassured.RestAssured.*
  • com.jayway.restassured.matcher.RestAssuredMatchers.*
  • org.hamcrest.Matchers.*

A Given, When, Then approach

To understand the basics of the REST-assured DSL, let's consider one of our tests (in UserControllerIT) that provides a short overview of REST-assured usage:

  @Test
  public void createUserBasicAuthAjax(){
    Response response = given()
    .header("X-Requested-With", "XMLHttpRequest")
    .contentType("application/json;charset=UTF-8")
    .accept("application/json")
    .body(userA)
    .when()
    .post(getHost() + CONTEXT_PATH + "/users");
    assertNotNull(response.getHeader("Location"));
  }

The given part of the statement is the HTTP Request specification. With REST-assured, some request headers like Content-Type or Accept can be defined in an intuitive way with contentType(…) and accept(…). Other headers can be reached with the generic .header(…). Request parameters and authentication can also be defined in a same fashion.

For POST and PUT requests, it is necessary to pass a body to the request. This body can either be plain JSON or XML or directly the Java object (as we did here). This body, as a Java object, will be converted by the library depending upon the content-type defined in the specification (JSON or XML).

After the HTTP Request specification, the when() statement provides information about the actual HTTP method and destination.

At this stage, the returned object allows us either to define expectations from a then() block or, as we did here, to retrieve the Response object from where constraints can be defined separately. In our test case, the Location header of the Response is expected to be filled.

There is more…

More information can be found at the following Cargo and REST-assured respective documentations:

About Cargo

For more information about the product and its integration with third-party systems, refer to https://codehaus-cargo.github.io/cargo/Home.html.

More REST-assured examples

For more examples, the REST-assured online Wiki provides plenty:

https://github.com/jayway/rest-assured/wiki/Usage

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset