Skip to content

cyrusinnovation/stubble

Repository files navigation

stubble

Stub server for testing HTTP interactions

Why Stubble?

Stubble was designed to support testing of HTTP interactions between applications. Typically, this would be a web application consuming web services from a backend server, but it could be used for any sort of application that needs to talk to another via HTTP. Stubble allows you to setup interactions as you would with a mock or stub library for unit testing. Rather than specifying method calls and return values, Stubble allows you to specify request conditions to match that, when matched, will cause the StubServer to return a specified response.

Testing is supported either within the same process, controlling the StubServer programmatically, or remotely, such as from an integration test running in a build, controlling the StubServer with a remote client over HTTP.

Usage

Starting the Server

Start the server by creating an instance with the desired port on which to serve interactions, then calling the start method:

val server = new StubServer(8082)
server.start()

Direct Control

Control of the StubServer is done via the StubServerControl interface/trait:

trait StubServerControl {
  def addInteraction(interaction: Interaction)
  def popInteractions()
  def pushInteractions()
}

The trait is mixed into both the StubServer class and the StubbleClient class, the latter being the mechanism for controlling interactions from a remote process. Interactions are setup by passing in an instance of the Interaction class:

server.addInteraction(Interaction(List(PathCondition("/")), Response(HttpResponseStatus.OK, Some(body))))

See also the StubbleClientTest and StubServerTest for more examples of setting up interactions.

Remote Control

Adding interactions remotely is much the same as on the server directly, but you must first create a client configured with the same port as the server:

val client = new StubbleClient(8082)

The Interaction Stack

Often during integration testing, you may want to start Stubble, start your application, then run a bunch of tests against the application. You probably want each test to be as self-contained as possible, so Stubble allows you to setup an interaction "stack frame" for each test. By calling pushInteractions() before each test and popInteractions() after each test, you can isolate tests from each other, throwing away any state you've setup after each test. A complete typical test might look like this (assuming the StubServer is started by the build, and here the ExampleApplication is small enough to be started for each test):

import com.cyrusinnovation.stubble.server._
import org.junit._
import org.jboss.netty.handler.codec.http.HttpResponseStatus
import org.junit.Assert._

class ExampleTest {
  var client: StubbleClient = new StubbleClient(8082)
  val app = new ExampleApplication

  @Before
  def setUp() {
    client.pushInteractions()
  }

  @After
  def tearDown() {
    client.popInteractions()
  }

  @Test
  def addsInteractionsToServer() {
    val interactions = List(Interaction(List(CookieCondition("type" -> "chocolate chip")), Response(HttpResponseStatus.OK, Some("gimme cookie!"))),
                            Interaction(List(PathCondition("/")), Response(HttpResponseStatus.OK, Some("Hello!"))))
    interactions.foreach(client.addInteraction(_))

    val appResponse = app.somethingThatNeedsABackendServer
    assertEquals("Something wonderful happened", appResponse)
  }
}

The interaction stack is sort of analogous to what JUnit does by initializing your test class before each test and throwing it away after, except that often you can't throw away your back end each time, because your application may be making requests of it outside your test flow. Stubble supports setting up the base, background interactions that might be happening outside the test flow by adding them to the first stack frame, which it will never pop off the stack. In other words, if you have interactions that need to be around for the duration of your server's lifetime, set those up before pushing interactions before the first time, and they'll stay around until the StubServer is shut down.

Maven Integration

Stubble is designed to fit easily into your Maven build. You can include the stub server and client code by adding a dependency on the stubble-core module:

    <dependency>
      <groupId>com.cyrusinnovation</groupId>
      <artifactId>stubble-core</artifactId>
      <version>0.0.1</version>
    </dependency>

A common way of running integration tests with maven is to create a submodule to contain integration tests, that depends on your main project module(s) and starts/stops your server instance. You may want the stub server to start before your server starts and stop after your server shuts down so that, for example, any interactions initiated at startup will succeed.

In order to start your server for integration testing, you might bind Cargo's start goal to the pre-integration-test phase and its stop goal to the post-integration-test phase, using the Surefire plugin to run tests in the integration-test phase. When binding a second plugin to the same phase (pre-integration-test, for example), however, Maven executes plugins in declaration order in the pom. In order to start before Cargo and stop after Cargo, then, Stubble provides two plugins, one each for start and stop actions. Therefore, use the start-stubble-plugin before Cargo in the pre-integration-test phase and the stop-stubble-plugin after Cargo in the post-integration-test phase.

If you don't understand any of this and haven't tried to sequence actions within a single phase in Maven, be thankful and just copy and paste the below example into your integration-test pom, modifying Cargo for your own application:

  <properties>
    <serverPort>8081</serverPort>
    <stubblePort>8082</stubblePort>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>com.cyrusinnovation</groupId>
        <artifactId>start-stubble-plugin</artifactId>
        <version>0.0.1</version>
        <configuration>
          <port>${stubblePort}</port>
        </configuration>
        <executions>
          <execution>
            <phase>pre-integration-test</phase>
            <goals>
              <goal>start</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skip>true</skip>
          <systemProperties>
            <property>
              <name>serverPort</name>
              <value>${serverPort}</value>
            </property>
            <property>
              <name>stubblePort</name>
              <value>${stubblePort}</value>
            </property>
          </systemProperties>
        </configuration>
        <executions>
          <execution>
            <id>integration-tests</id>
            <phase>integration-test</phase>
            <goals>
              <goal>test</goal>
            </goals>
            <configuration>
              <skip>false</skip>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.codehaus.cargo</groupId>
        <artifactId>cargo-maven2-plugin</artifactId>
        <version>1.2.0</version>
        <executions>
          <execution>
            <id>start-container</id>
            <phase>pre-integration-test</phase>
            <goals>
              <goal>start</goal>
            </goals>
          </execution>
          <execution>
            <id>stop-container</id>
            <phase>post-integration-test</phase>
            <goals>
              <goal>stop</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <wait>false</wait>
          <container>
            <containerId>tomcat7x</containerId>
            <zipUrlInstaller>
              <url>
                http://mirror.cc.columbia.edu/pub/software/apache/tomcat/tomcat-7/v7.0.27/bin/apache-tomcat-7.0.27.zip
              </url>
              <downloadDir>${java.io.tmpdir}/cargoinstalls.${user.name}</downloadDir>
            </zipUrlInstaller>
            <systemProperties>
              <stubblePort>${stubblePort}</stubblePort>
            </systemProperties>
          </container>
          <configuration>
            <home>${project.build.directory}/tomcat7x</home>
            <properties>
              <cargo.servlet.port>${serverPort}</cargo.servlet.port>
              <cargo.tomcat.ajp.port>8010</cargo.tomcat.ajp.port>
            </properties>
            <deployables>
              <deployable>
                <groupId>com.cyrusinnovation</groupId>
                <artifactId>test-server</artifactId>
                <type>war</type>
                <properties>
                  <context>/</context>
                </properties>
              </deployable>
            </deployables>
          </configuration>
        </configuration>
      </plugin>
      <plugin>
        <groupId>com.cyrusinnovation</groupId>
        <artifactId>stop-stubble-plugin</artifactId>
        <version>0.0.1</version>
        <configuration>
          <port>${stubblePort}</port>
        </configuration>
        <executions>
          <execution>
            <phase>post-integration-test</phase>
            <goals>
              <goal>stop</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

About

Stub server for testing HTTP interactions

Resources

Stars

Watchers

Forks

Packages

No packages published