-
Notifications
You must be signed in to change notification settings - Fork 0
Testing
One of sprays core design goals is optimal testability of the created web services. Since actor-based systems can sometimes be cumbersome to test spray fosters the separation of route logic from actor code. If you design your web service routes with sprays internal routing DSL your service will be fully testable without the need to fire up actors or any type of container.
spray-server comes with the SprayTest trait that enables convenient route and service testing using testing frameworks like scalatest, specs or specs2.
Here is an example of what spray tests can look like:
package cc.spray.examples.calculator
import org.specs2.mutable._
import cc.spray._
import test._
import http._
import HttpMethods._
import HttpHeaders._
import MediaTypes._
import StatusCodes._
class CalculatorServiceSpec extends Specification with SprayTest with CalculatorService with DontDetach {
"The Calculator service" should {
"calculate correctly" in {
"for 'add's" in {
testService(HttpRequest(GET, "/add/35/7.2")) {
calculatorService
}.response.content.as[String] mustEqual Right("<double>42.2</double>")
}
"for 'substract's" in {
testService(HttpRequest(GET, "/substract/35.1/7", headers = List(`Accept`(`text/plain`)))) {
calculatorService
}.response.content.as[String] mustEqual Right("28.1")
}
}
"use the given onDivZero parameter value on division by zero" in {
testService(HttpRequest(GET, "/divide/12/0?onDivZero=nah!")) {
calculatorService
}.response.content.as[String] mustEqual Right("nah!")
}
"return a MethodNotAllowed error for POST requests to '/substract/123/234'" in {
testService(HttpRequest(POST, "/substract/123/234")) {
calculatorService
}.response mustEqual HttpResponse(MethodNotAllowed, "HTTP method not allowed, supported methods: GET")
}
"leave GET requests to other paths unhandled" in {
testService(HttpRequest(GET, "/multiply/x/22")) {
calculatorService
}.handled must beFalse
}
}
}This example uses specs2 to test a calculator service (actually it is an excerpt from the calculator example,
see Example Projects). You are free to choose any testing framework you like, the only requirement is that the
type you mix the SprayTest trait into must define a fail(msg: String): Nothing or failure(msg: String): Nothing
method (which scalatest, specs and specs2 do).
The SprayTest trait defines two main test methods: test and testService.
The first is used for route testing while the second can also test the HttpService actor logic you might have
customized.
This is how you test a route with the test method:
val result = test(HttpRequest(...)) {
route
}The test method takes as parameter an HttpRequest object that it "shoots" into your route.
There are three possible outcomes:
- The request is completed
- The request is rejected
- The request is not responded to at all (i.e. the route did not call
completeorrejecton the RequestContext)
The last case is usually an error, so spray automatically verifies that your route either completes or rejects the
request. Normally you simply define assertions on result.response (which is an HttpResponse object) or on
result.rejections (which is a set of Rejections).
If you do not want to specify tests on the Rejection level but rather on the very outside of your service, where
rejections have already been translated into HttpResponses you use the testService method. Just like
in the case of the test method you usually define assertions on the response member of the object returned by
testService.
If you have derived your own CustomHttpServiceLogic that you would like to test, (see the Custom Error Responses
chapter for more on this) create an implicit conversion similar to this:
implicit def customWrapRootRoute(rootRoute: Route): ServiceTest = {
new CustomHttpServiceLogic with ServiceTest {
val route = routeRoute
}
}testService will use your implicit conversion (if it's in scope) and allow you to test your own custom rejection
handling code.
sprays detach directive interferes with proper testing since it decouples the route execution from the thread the
testing logic is running in, causing your route to appear to not respond to the request. You should therefore disable
detaching in your tests by mixing in the DontDetach trait into the class or trait building your service.
All that this trait does is overriding the detach directive to not actually detach.
- Home
- Requirements
- spray-server
- ... Getting Started
- ... Key Concepts
- ...... Request Lifecycle
- ...... Routes
- ...... Directives
- ...... Composing Directives
- ...... Rejections
- ...... Marshalling and Unmarshalling
- ... Predefined Directives
- ...... Method Filters
- ...... Path Filters
- ...... Parameter Filters
- ...... Form-Field Filters
- ...... Marshalling/Unmarshalling
- ...... Caching
- ...... Detach
- ...... Encoding/Decoding
- ...... Authentication/Authorization
- ...... File and Resource Directives
- ...... Misc Directives
- ... Advanced Topics
- ...... Case Class Extraction
- ...... Custom Directives
- ...... Custom Media Types
- ...... Custom Error Responses
- ... Configuration
- ... Testing
- ... Example Projects
- spray-client
- Patch Policy
- Credits
- Sponsors