#Testing squbs Applications All tests on squbs have been written using ScalaTest 2.x. Specs2 has not yet been tested.
##Dependencies
To use test utilities mentioned in this documentation,, simply add the following dependencies in your build.sbt
file or Scala build script:
"org.squbs" %% "squbs-testkit" % squbsVersion
##CustomTestKit
The CustomTestKit
is used for starting a full blown squbs instance needed for testing bits and pieces of applications. CustomTestKit
is simple to use and needs no configuration by default, yet allows customizations and flexibility for your tests. With CustomTestKit
you can start any number of ActorSystem
and Unicomplex
instances (one per ActorSystem
) with different configurations - all on the same JVM. Here are some features:
- It automatically sets actor system name to spec/test class name for simplicity and also to ensure
ActorSystem
instances do not conflict. But, it also allows actor system name to be passed in the constructor. - Tests extending
CustomTestKit
can run in parallel in the same JVM. - Starts and stops squbs automatically.
- Starts well-known actors and service in
src/main/resources/META-INF/squbs-meta.conf
andsrc/test/resources/META-INF/squbs-meta.conf
by default. But, allows passingresources
to be scanned in the constructor. - Allows custom configuration to be passed in the constructor.
Here is an example usage of CustomTestKit
in ScalaTest:
class SampleSpec extends CustomTestKit with FlatSpecLike with Matchers {
it should "start the system" in {
system.startTime should be > 0L
}
}
Both TestNG and JUnit are supported for Java users:
public class SampleTest extends AbstractCustomTestKit {
@Test
public void testSystemStartTime() {
Assert.assertTrue(system().startTime() > 0L);
}
}
###Passing configuration to CustomTestKit
If you would like to customize the actor system configuration, you can pass a Config
object to CustomTestKit
:
object SampleSpec {
val config = ConfigFactory.parseString {
"""
|akka {
| loglevel = "DEBUG"
|}
""".stripMargin
}
}
class SampleSpec extends CustomTestKit(SampleSpec.config) with FlatSpecLike with Matchers {
it should "set akka log level to the value defined in config" in {
system.settings.config.getString("akka.loglevel") shouldEqual "DEBUG"
}
}
Here is the TestNG/JUnit version of this test:
public class SampleTest extends AbstractCustomTestKit {
public SampleTest() {
super(TestConfig.config);
}
@Test
public void testAkkaLogLevel() {
Assert.assertEquals(system().settings().config().getString("akka.loglevel"), "DEBUG");
}
private static class TestConfig {
private static Config config = ConfigFactory.parseString("akka.loglevel = DEBUG");
}
}
The following sections show customizations only in ScalaTest; however, all these customizations are supported for TestNG and JUnit as well. For customizations, provide a public
constructor in your TestNG/JUnit tests and call super
with custom parameters. Check squbs-testkit/src/test/java/org/squbs/testkit/japi for more TestNG and JUnit samples.
Specifically for JUnit, avoid setting actor system name in your tests (letting CustomTestKit
set the actor system name is in general a good practice though). JUnit creates a new fixture instance for each @Test
method which potentially causes actor system conflicts. AbstractCustomTestKit
avoids this by appending an incremented integer to each actor system name.
###Starting well-known actors and services with CustomTestKit
CustomTestKit
will automatically start well-known actors and services in src/test/resources/META-INF/squbs-meta.conf
(see Bootstrapping squbs). However, if you would like provide different resource paths, you can do so by passing a Seq
of resources to the constructor. withClassPath
controls whether to scan entire test classpath for META-INF/squbs-meta.conf
files or not.
object SampleSpec {
val resources = Seq(getClass.getClassLoader.getResource("").getPath + "/SampleSpec/META-INF/squbs-meta.conf")
}
class SampleSpec extends CustomTestKit(SampleSpec.resources, withClassPath = false)
with FlatSpecLike with Matchers {
// Write tests here
}
Please note, CustomTestKit
allows passing config
and resources
together as well.
####Port binding in tests
Starting services requires port binding. To prevent port conflicts, we should let the system pick a port by setting listeners' bind-port
to 0, e.g., default-listener.bind-port = 0
(this is what CustomTestKit
sets by default). squbs-testkit
comes with a trait
named PortGetter
that allows retrieving the port picked by the system. CustomTestKit
comes with PortGetter
already mixed in, so you can use port
value.
class SampleSpec extends CustomTestKit(SampleSpec.resources)
"PortGetter" should "retrieve the port" in {
port should be > 0
}
}
By default, PortGetter
returns default-listener
's port, which is the most common one. If you need to retrieve another listener's bind port, you can either override listener
method or pass listener name to port
method:
class SampleSpec extends CustomTestKit(SampleSpec.resources)
override def listener = "my-listener"
"PortGetter" should "return the specified listener's port" in {
port should not equal port("default-listener")
port shouldEqual port("my-listener")
}
}
###Manual UnicomplexBoot
initialization
CustomTestKit
allows a UnicomplexBoot
instance to be passed as well. This allows full customization of the bootrap. Please see Bootstrapping squbs for more information on booting the Unicomplex.
object SampleSpec {
val config = ConfigFactory.parseString(
s"""
|squbs {
| actorsystem-name = SampleSpec # should be unique to prevent collision with other tests running in parallel
| ${JMX.prefixConfig} = true # to prevent JMX name collision, if you are doing any JMX testing
|}
""".stripMargin
)
val resource = getClass.getClassLoader.getResource("").getPath + "/SampleSpec/META-INF/squbs-meta.conf"
val boot = UnicomplexBoot(config)
.createUsing {(name, config) => ActorSystem(name, config)}
.scanResources(resource)
.initExtensions.start()
}
class SampleSpec extends CustomTestKit(SampleSpec.boot) with FunSpecLike with Matchers {
// Write your tests here.
}
##Testing Spray/Akka Http Routes using Spray/Akka Http TestKit
The spray-testkit
or akka-http-testkit
needs to be added to the dependencies in order to test routes. Please add the followings to your dependencies:
For Spray:
"io.spray" %% "spray-testkit" % sprayV % "test"
For Akka-Http:
"com.typesafe.akka" %% "akka-http-testkit" % akkaV % "test"
As you specify routes in squbs by extending the RouteDefinition
trait which squbs will compose with actors behind
the scenes, it can be difficult to construct routes for use with the Spray/Akka Http TestKit test DSL. TestRoute
is provided
for constructing and obtaining routes from the RouteDefinition
. To use it, just pass the RouteDefinition
as a type
parameter to TestRoute
. This will obtain you a fully configured and functional route for the test DSL as can be seen
in the example below.
Specifying the RouteDefinition
package com.myorg.mypkg
import org.squbs.unicomplex.RouteDefinition
import spray.routing.Directives._
class MyRoute extends RouteDefinition {
val route =
path("ping") {
get {
complete {
"pong"
}
}
}
}
Implementing the test, obtaining route from TestRoute[MyRoute]
:
package com.myorg.mypkg
import org.scalatest.{Matchers, FlatSpecLike}
import org.squbs.testkit.TestRoute
import spray.testkit.ScalatestRouteTest
class MyRouteTest extends FlatSpecLike with Matchers with ScalatestRouteTest {
val route = TestRoute[MyRoute]
it should "return pong on a ping" in {
Get("/ping") ~> route ~> check {
responseAs[String] should be ("pong")
}
}
}
Alternatively, you may also want to pass a web context to your route. This can be done by passing it to TestRoute
as follows:
val route = TestRoute[MyRoute](webContext = "mycontext")
or just pass "mycontext"
without the parameter name. The TestRoute
signature without parameters is equivalent to passing the root context ""
.
###Using TestRoute with CustomTestKit
A need may arise to bootstrap Unicomplex
while testing with TestRoute
, such as when:
- a squbs well-known actor is involved in request handling.
- The Actor Registry is used during request handling.
Using Akka's TestKit
together with ScalatestRouteTest
can be tricky as they have conflicting initialization. squbs provides a test utility named CustomRouteTestKit
to solve this problem. CustomRouteTestKit
supports all the APIs provided by CustomTestKit
. Here is an example usage of TestRoute
with CustomRouteTestKit
:
class MyRouteTest extends CustomRouteTestKit with FlatSpecLike with Matchers {
it should "return response from well-known actor" in {
val route = TestRoute[ReverserRoute]
Get("/msg/hello") ~> route ~> check {
responseAs[String] should be ("hello".reverse)
}
}
}
class ReverserRoute extends RouteDefinition {
import akka.pattern.ask
import Timeouts._
import context.dispatcher
val route =
path("msg" / Segment) { msg =>
get {
onComplete((context.actorSelection("/user/mycube/reverser") ? msg).mapTo[String]) {
case Success(value) => complete(value)
case Failure(ex) => complete(s"An error occurred: ${ex.getMessage}")
}
}
}
}
Note: To use CustomRouteTestKit
, please ensure the Spray or Akka Http testkit is in your dependencies as described above.