-
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:
import org.specs2.mutable._
import test._
import http._
import HttpMethods._
import HttpHeaders._
import MediaTypes._
import StatusCodes._
class CalculatorServiceSpec extends Specification with SprayTest with CalculatorService {
"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")
}
"for 'multiply's" in {
testService(HttpRequest(GET, "/multiply/35/7")) {
calculatorService
}.response.content.as[String] mustEqual Right("<double>245.0</double>")
}
"for 'divide's" in {
testService(HttpRequest(GET, "/divide/35/7")) {
calculatorService
}.response.content.as[String] mustEqual Right("<double>5.0</double>")
}
}
"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 a custom RejectionHandler you might have in place
(see the Custom Error Responses chapter for more on this).
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.
In order to feed your custom RejectionHandler to the testService method just make it available implicitly.
testService will pick it up automatically:
val myRejectionHandler: RejectionHandler = {
case AuthenticationFailedRejection(realm) :: _ =>
HttpResponse(Unauthorized, "Your credentials are pathetic!")
}
implicit val rejectionHandler =
myRejectionHandler orElse RejectionHandler.Default
"Auth errors should be offensive" in {
testService(HttpRequest(GET, "/")) {
reject(AuthenticationFailedRejection("Off Limits")
}.response mustEqual HttpResponse(Unauthorized,
"Your credentials are pathetic")
}- 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