Skip to content
Paul Heymann edited this page Feb 14, 2017 · 17 revisions

This is the rrt Wiki. Here you can find some more information and examples of how to use this library. For further question join the Gitter channel.

Basic Test Structure

To write a refactoring test with this library is quite easy. First you define which data you need to generate your requests:

import com.github.pheymann.rrt._

val myTest = for {
  userNames <- genStaticData("Luke", "Anakin")
  userAges  <- genPositiveInts(100)

  result    <- ...

Here we say we need some user names and provide a static set and an age generator. And as a user age of 0 or 200 doesn't makes much sense we say it should be a non-zero integer below 100.

Next we describe how to create requests from this data:

  result <- testGet { _ =>
    val uri = s"/my/service/user/${userNames()}"
    val params = Params().add("age", userAges)

    uri |+| params
  }
} yield result

We say we want to test a GET endpoint with the URI /my/service/user/:userName?:age. Now the last step is to execute our test. Therefore we have to provide a configuration:

val config = TestConfig("my test", ServiceConfig("newservice.com", 80), ServiceConfig("oldservice.com", 80))
               .withRepetitions(10) 

Finally we run the test case:

assert(checkAndLog(myTest.runSeq(config)))

Here we executed the test, logged possible errors and checked the result.

You can use rrt as stand-alone library in a script or wrap it into some automated test framework like scalatest or specs.

Library API

Test Configuration

The test configuration is done in-code by using the TestConfig class.

Usage:

val config = TestConfig("test-name", ServiceConfig("url-new-service.com", 80), ServiceConfig("url-old-service.com", 80)

The above fields are mandatory. Furthermore it makes sense to put the ServiceConfig in some trait or object to reuse it for multiple tests.

The basic configuration can be extended with:

  • withRepetitions: How often the test is repeated.
  • withHeaders: Adds standard headers to every request.
  • withIgnoreByRegex: List of regexes which will replace elements in the response body with an empty String.
  • withIgnoreJsonKeys: List of Json keys which will be removed from the response jsons together with values.
  • withThrottling: Defines a maximum request rate.
  • withTimeout: Sets a timeout value for the requests.
  • withDatabase: Configures a single database connection.
  • showJsonDiff: Generates and shows the JSON differences of the response bodies when set to true.

Random Value Generators

You can generate random values by using the following generators.

Usage:

for {
  userAges <- genPositiveInts(100)
}
  • genStaticData[A](values: A*): You provide a list of static values and the generator is selecting elements randomly from it.
  • genInts(max: Option[Int]): A generator for Int values with an optional maximum bound.
  • genPositiveInts(max: Option[Int]): A generator for positive Int values (0 <) with an optional maximum bound.
  • genLongs(max: Option[Int]): A generator for Long values with an optional maximum bound.
  • genDoubles(max: Option[Int]): A generator for Double values with an optional maximum bound.

Random Database Selectors

You can also provide data from some database if randomly generated values aren't sufficient, e.g. ids.

Usage:

for {
  userNames <- retrieveStrings(100).from("user_table", "id", "name")
}

Furthermore you have to provide a configuration for the database connection:

val config = TestConfig(...).withDatabase(MySQL, "com.mysql.jdbc.Driver", "jdbc:mysql://localhost", "user", "password")

Or if you use Play you can just include you database conf:

import com.github.pheymann.rrt.util.play._

val config = TestConfig(...).withDatabase(MySQL, "my-database")
  • retrieveInts(maxSize: Int): Randomly selects at most maxSize Int elements.
  • retrieveLongs(maxSize: Int): Randomly selects at most maxSize Long elements.
  • retrieveDouble(maxSize: Int): Randomly selects at most maxSize Double elements.
  • retrieveStrings(maxSize: Int): Randomly selects at most maxSize String elements.

Sequence and Options of Random Values

You can also generate a sequence or Option of random values:

Usage:

for {
  userNames <- ...
  results   <- testGet { implicit rand =>
    val names = userNames.toSeq(10)
    val nameOpt = userNames.toOpt
    ...
  }
} yield result

You have to provide the RandomUtil instance as implicit function parameter.

Parameter Builder

There is some syntactic sugar for building the parameters Map[String, String] called Params.

Usage:

val params = Params().add("age", ageGenerator).add("name", nameGenerator)

You can also provide a Map and add it to the constructor of the class. Thus, you are able the reuse common parameters.

URI only requests

Usage:

  result <- testPost { _ =>
    ...
    uri
  }

With Parameters

  result <- testPost { _ =>
    ...
    uri |+| params
  }

Add Request Bodies

Usage:

  result <- testPost { _ =>
    ...
    uri |+| params |+| jsonString |=| ContentTypes.`application/json`
  }

Or without parameters:

  result <- testPost { _ =>
    ...
    uri |+| jsonString |=| ContentTypes.`application/json`
  }

Provide your own Body Comparison Function

The String comparison function can be replaced by providing an own implementation.

Usage:

com.github.pheymann.rrt.util._

val myComparison: BodyComparison = (actual, expected, config) => {
  //do my comparison here
  ...

  // you can also use `FailedWithDiff(element, diff)`
  if (failed)
    Some(FailedWithValues("body", actual, expected))
  else
    None
}

val myTest = for {...}

assert(checkAndLog(myTest.runSeq(config, comparison = myComparison)))