This is an automated email from the ASF dual-hosted git repository.

fanningpj pushed a commit to branch wip-adding-tests
in repository 
https://gitbox.apache.org/repos/asf/incubator-pekko-http-quickstart-scala-g8.git

commit 5f224691843c10d7c71fe9a0b8f1136cc4fda94c
Author: Konrad `ktoso` Malawski <[email protected]>
AuthorDate: Mon Aug 7 22:53:03 2017 +0900

    Added testing docs and some more separation
---
 .gitignore                                         |   1 +
 docs/src/main/paradox/backend-actor.md             |  15 +++
 docs/src/main/paradox/backend.md                   |  10 --
 .../paradox/{server-class.md => http-server.md}    |  68 ++++++++++----
 docs/src/main/paradox/index.md                     |   8 +-
 docs/src/main/paradox/json.md                      |  32 +++++--
 docs/src/main/paradox/running-the-application.md   |  16 ++--
 docs/src/main/paradox/testing-routes.md            | 104 +++++++++++++++++++++
 src/main/g8/.gitignore                             |   2 +
 src/main/g8/build.sbt                              |   4 +-
 .../lightbend/akka/http/sample/JsonSupport.scala   |   8 +-
 .../akka/http/sample/QuickstartServer.scala        |  84 ++++-------------
 .../akka/http/sample/UserRegistryActor.scala       |  17 ++--
 .../lightbend/akka/http/sample/UserRoutes.scala    |  96 +++++++++++++++++++
 .../akka/http/sample/UserRoutesSpec.scala          |  86 +++++++++++++++++
 15 files changed, 422 insertions(+), 129 deletions(-)

diff --git a/.gitignore b/.gitignore
index 2704fde..afaa680 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 target/
+.idea
 boot/
 lib_managed/
 src_managed/
diff --git a/docs/src/main/paradox/backend-actor.md 
b/docs/src/main/paradox/backend-actor.md
new file mode 100644
index 0000000..2bdbe56
--- /dev/null
+++ b/docs/src/main/paradox/backend-actor.md
@@ -0,0 +1,15 @@
+Backend Actor logic
+-------------------
+
+In this example, the backend only uses one basic actor. In a real system, we 
would have many actors interacting with each other and perhaps, multiple data 
stores and microservices. 
+
+An interesting side-note to add here is perhaps about when using actors in 
applications like this adds value over just providing functions that would 
return Futures.
+In fact, if your logic is stateless and very simple request/reply style, you 
may not need to back it with an Actor. actors do shine however when you need to 
keep some form of state and allow various requests to access something in (or 
*through*) an Actor. The other stellar feature of actors, that futures would 
not handle, is scaling-out onto a cluster very easily, by using [Cluster 
Sharding](http://doc.akka.io/docs/akka/current/scala/cluster-sharding.html) or 
other [location-transparent] [...]
+
+However, the focus of this tutorial is on how to interact with an Actor 
backend from within Akka HTTP -- not on the actor itself, so we'll keep it very 
simple.
+ 
+The sample code in the `UserRegistryActor` is very simple. It keeps registered 
users in a `Set`. Once it receives messages it matches them to the defined 
cases to determine which action to take:
+
+@@snip 
[UserRegistryActor.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala)
+
+If you feel you need to brush up on your Akka Actor knowledge, the [Getting 
Started Guide]((http://doc.akka.io/docs/akka/current/scala/guide/index.html)) 
reviews actor concepts in the context of a simple Internet of Things (IoT) 
example.
diff --git a/docs/src/main/paradox/backend.md b/docs/src/main/paradox/backend.md
deleted file mode 100644
index c0505a9..0000000
--- a/docs/src/main/paradox/backend.md
+++ /dev/null
@@ -1,10 +0,0 @@
-Backend logic
--------------
-
-In this example, the backend only uses one basic actor. In a real system, we 
would have many actors interacting with each other and perhaps, multiple data 
stores and microservices. However, the focus of this tutorial is on how to 
interact with a backend from within Akka HTTP -- not on the actor itself.
-
-The sample code in the `UserRegistryActor` is very simple. It keeps registered 
users in a `Set`. Once it receives messages it matches them to the defined 
cases to determine which action to take:
-
-@@snip 
[UserRegistryActor.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala)
-
-If you feel you need to brush up on your Akka Actor knowledge, the [Getting 
Started Guide]((http://doc.akka.io/docs/akka/current/scala/guide/index.html)) 
reviews actor concepts in the context of a simple Internet of Things (IoT) 
example.
diff --git a/docs/src/main/paradox/server-class.md 
b/docs/src/main/paradox/http-server.md
similarity index 57%
rename from docs/src/main/paradox/server-class.md
rename to docs/src/main/paradox/http-server.md
index b4957fd..6649b44 100644
--- a/docs/src/main/paradox/server-class.md
+++ b/docs/src/main/paradox/http-server.md
@@ -1,23 +1,33 @@
-Server logic
-------------
+HTTP Server logic
+-----------------
 
-The main class, `QuickstartServer`, is runnable because it extends `App`, as 
shown in the following snippet. We will discuss the trait `JsonSupport` later.
+The main class, `QuickstartServer`, is runnable because it extends `App`, as 
shown in the following snippet. 
+This class is intended to "bring it all together", it is the main class that 
will run the application, as well 
+as the class that should bootstrap all actors and other dependencies (database 
connections etc). 
 
 @@snip 
[QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala)
 { #main-class }
 
+Notice that we've separated out the `UserRoutes` trait, in which we'll put all 
our actual route definitions.
+This is a good pattern to follow, especially once your application starts to 
grow and you'll need some form of 
+compartmentalizing them into groups of routes handling specific parts of the 
exposed API.
+
+
 ## Binding endpoints
 
 Each Akka HTTP `Route` contains one or more 
`akka.http.scaladsl.server.Directives`, such as: `path`, `get`, `post`, 
`complete`, etc. There is also a [low-level 
API](http://doc.akka.io/docs/akka-http/current/scala/http/low-level-server-side-api.html)
 that allows to inspect requests and create responses manually. For the user 
registry service, the example needs to support the actions listed below. For 
each, we can identify a path, the HTTP method, and return value:
 
-|Functionality       | Path       | HTTP method     | Returns              |
-|--------------------|------------|-----------------|----------------------|
-| Create a user      | /users     | POST            | Confirmation message |
-| Retrieve a user    | /users/$ID | GET             | JSON payload         |
-| Remove a user      | /users/$ID | DELETE          | Confirmation message |
-| Retrieve all users | /users     | GET             | JSON payload         |
+| Functionality      | HTTP Method | Path       | Returns              |
+|--------------------|-------------|------------|----------------------|
+| Create a user      | POST        | /users     | Confirmation message |
+| Retrieve a user    | GET         | /users/$ID | JSON payload         |
+| Remove a user      | DELETE      | /users/$ID | Confirmation message |
+| Retrieve all users | GET         | /users     | JSON payload         |
+
+In the `QuickstartServer` source file, the definition of the `Route` delegates 
to the routes defined in `UserRoutes`:
+`lazy val routes: Route = userRoutes`.
 
-In the `QuickstartServer` source file, the definition of the `Route` begins 
with the line:
-`lazy val routes: Route =`.
+In larger applications this is where we'd combine the various routes of our 
application into a big route that is concatenating
+the various routes of our services. We'd do this using the concat directive 
like this: `val route = concat(userRoutes, healthCheckRoutes, ...)`
 
 Let's look at the pieces of the example `Route` that bind the endpoints, HTTP 
methods, and message or payload for each action.
 
@@ -25,15 +35,23 @@ Let's look at the pieces of the example `Route` that bind 
the endpoints, HTTP me
 
 The definition of the endpoint to retrieve and create users look like the 
following:
 
-@@snip 
[QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala)
 { #users-get-post }
+@@snip 
[UserRoutes.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRoutes.scala)
 { #users-get-post }
 
+A Route is constructed by nesting various *directives* which route an incoming 
request to the apropriate handler block.
 Note the following building blocks from the snippet:
 
 **Generic functionality**
 
+The following directives are used in the above example:
+
 * `pathPrefix("users")` : the path that is used to match the incoming request 
against.
 * `pathEnd` : used on an inner-level to discriminate “path already fully 
matched” from other alternatives. Will, in this case, match on the "users" path.
-* `~`: concatenates two or more route alternatives. Routes are attempted one 
after another. If a route rejects a request, the next route in the chain is 
attempted. This continues until a route in the chain produces a response. If 
all route alternatives reject the request, the concatenated route rejects the 
route as well. In that case, route alternatives on the next higher level are 
attempted. If the root level route rejects the request as well, then an error 
response is returned that con [...]
+* `concat`: concatenates two or more route alternatives. Routes are attempted 
one after another. If a route rejects a request, the next route in the chain is 
attempted. This continues until a route in the chain produces a response. If 
all route alternatives reject the request, the concatenated route rejects the 
route as well. In that case, route alternatives on the next higher level are 
attempted. If the root level route rejects the request as well, then an error 
response is returned tha [...]
+    * This can also be achieved using the `~` operator, like this: 
`exampleRoute ~ anotherRoute`. 
+    However this method is slightly more error-prone since forgetting to add 
the `~` between routes in subsequent lines 
+    will not result in a compile error (as it would when using the `concat` 
directive) resulting in only the "last" route to be returned. <br/>
+    <br/>
+    In short other words: you may see the `~` operator used in Akka HTTP apps, 
however it is recommended to use the `concat` directive as safer alternative. 
 
 **Retrieving users**
 
@@ -44,30 +62,32 @@ Note the following building blocks from the snippet:
 
 * `post` : matches against `POST` HTTP method.
 * `entity(as[User])` : converts the HTTP request body into a domain object of 
type User. Implicitly, we assume that the request contains application/json 
content. We will look at how this works in the @ref:[JSON](json.md) section.
-* `complete` : completes a request which means creating and returning a 
response from the arguments. Note, how the tuple (StatusCodes.Created, "...") 
of type (StatusCode, String) is implicitly converted to a response with the 
given status code and a text/plain body with the given string.
+* `complete` : completes a request which means creating and returning a 
response from the arguments. Note, how the tuple `(StatusCodes.Created, "...")` 
of type `(StatusCode, String)` is implicitly converted to a response with the 
given status code and a text/plain body with the given string.
 
 ### Retrieving and removing a user
 
 Next, the example defines how to retrieve and remove a user. In this case, the 
URI must include the user's id in the form: `/users/$ID`. See if you can 
identify the code that handles that in the following snippet. This part of the 
route includes logic for both the GET and the DELETE methods.
 
-@@snip 
[QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala)
 { #users-get-delete }
+@@snip 
[QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRoutes.scala)
 { #users-get-delete }
 
 This part of the `Route` contains the following:
 
 **Generic functionality**
 
+The following directives are used in the above example:
+
 * `pathPrefix("users")` : the path that is used to match the incoming request 
against.
+* `concat`: concatenates two or more route alternatives. Routes are attempted 
one after another. If a route rejects a request, the next route in the chain is 
attempted. This continues until a route in the chain produces a response. 
 * `path(Segment) { => user` : this bit of code matches against URIs of the 
exact format `/users/$ID` and the `Segment` is automatically extracted into the 
`user` variable so that we can get to the value passed in the URI. For example 
`/users/Bruce` will populate the `user` variable with the value "Bruce." There 
is plenty of more features available for handling of URIs, see [pattern 
matchers](http://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/path-matchers.html#basic-pathmat
 [...]
-* `~`: concatenates two or more route alternatives. Routes are attempted one 
after another. If a route rejects a request, the next route in the chain is 
attempted. This continues until a route in the chain produces a response. If 
all route alternatives reject the request, the concatenated route rejects the 
route as well. In that case, route alternatives on the next higher level are 
attempted. If the root level route rejects the request as well, then an error 
response is returned that con [...]
 
 **Retrieving a user**
 
 * `get` : matches against `GET` HTTP method.
 * `complete` : completes a request which means creating and returning a 
response from the arguments.
 
-Let's break down the "business logic":
+Let's break down the logic handling the incoming request:
 
-@@snip 
[QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala)
 { #retrieve-user-info }
+@@snip 
[UserRoutes.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRoutes.scala)
 { #retrieve-user-info }
 
 The `rejectEmptyResponse` here above is a convenience method that 
automatically unwraps a future, handles an `Option` by converting `Some` into a 
successful response, returns a HTTP status code 404 for `None`, and passes on 
to the `ExceptionHandler` in case of an error, which returns the HTTP status 
code 500 by default.
 
@@ -75,13 +95,21 @@ The `rejectEmptyResponse` here above is a convenience 
method that automatically
 
 * `delete` : matches against the Http directive `DELETE`.
 
-The "business logic" for when deleting a user is straight forward; send an 
instruction about removing a user to the user registry actor, wait for the 
response and return an appropriate HTTP status code to the client.
+The logic for handling delete requests is as follows:
+
+@@snip 
[UserRoutes.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRoutes.scala)
 { #users-delete-logic }
+
+So we send an instruction about removing a user to the user registry actor, 
wait for the response and return an appropriate HTTP status code to the client.
+
 
 ## The complete Route
 
 Below is the complete `Route` definition from the sample application:
 
-@@snip 
[QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala)
 { #all-routes }
+@@snip 
[UserRoutes.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRoutes.scala)
 { #all-routes }
+
+Note that one might want to separate those routes into smaller route values 
and `concat` them together into the `userRoutes`
+value - in a similar fashion like we do in the `QuickstartServer` leading to a 
bit less "dense" code.
 
 ## Binding the HTTP server
 
diff --git a/docs/src/main/paradox/index.md b/docs/src/main/paradox/index.md
index 6877f63..0c4fff8 100644
--- a/docs/src/main/paradox/index.md
+++ b/docs/src/main/paradox/index.md
@@ -175,7 +175,8 @@ Congratulations, you just ran and exercised your first Akka 
HTTP app! You got a
 
 The example is implemented in the following three source files:
 
-* `QuickstartServer.scala` -- contains the main class and Akka HTTP `routes`.
+* `QuickstartServer.scala` -- contains the main class which sets-up and all 
actors, it runs the Akka HTTP `routes`.
+* `UserRoutes.scala` -- contains Akka HTTP `routes` that the Server will serve.
 * `UserRegistryActor.scala` -- implements the actor that handles registration.
 * `JsonSupport.scala` -- converts the JSON data from requests into Scala types 
and from Scala types into JSON responses.
 
@@ -183,10 +184,11 @@ First, let's dissect the backend logic.
 
 @@@index
 
-* [The backend](backend.md)
-* [The server](server-class.md)
+* [The Actor backend](backend-actor.md)
+* [The HTTP server](http-server.md)
 * [JSON](json.md)
 * [Running the application](running-the-application.md)
+* [Testing Routes](testing-routes.md)
 * [IntelliJ IDEA](intellij-idea.md)
 
 @@@
diff --git a/docs/src/main/paradox/json.md b/docs/src/main/paradox/json.md
index 69c2689..1a6aa12 100644
--- a/docs/src/main/paradox/json.md
+++ b/docs/src/main/paradox/json.md
@@ -1,22 +1,40 @@
-JSON conversion
----------------
+JSON marshalling
+----------------
 
 When exercising the app, you interacted with JSON payloads. How does the 
example app convert data between JSON format and data that can be used by Scala 
classes? The answer begins in the server class definition `JsonSupport` trait:
 
-@@snip 
[QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala)
 { #main-class }
+@@snip 
[UserRoutes.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRoutes.scala)
 { #user-routes-class }
 
 This trait is implemented in the `JsonSupport.scala` source file:
 
 @@snip 
[JsonSupport.scala]($g8src$/scala/com/lightbend/akka/http/sample/JsonSupport.scala)
 
+We're using the [Spray JSON](https://github.com/spray/spray-json) library 
here, which allows us to define json marshallers
+(or `formats` how Spray JSON calls them) in a type-safe way. In other words, 
if we don't provide a format instance for 
+a type, yet we'd try to return it in a route by calling `complete(someValue)` 
the code would not compile - saying that
+it does not know how to marshal the `SomeValue` type. This has the up-side of 
us being completely in control over what 
+we want to expose, and not exposing some type accidentally in our API.
+
 To handle the two different payloads, the trait defines two implicit values; 
`userJsonFormat` and `usersJsonFormat`. Defining the formatters as `implicit` 
ensures that the compiler can map the formatting functionality with the case 
classes to convert.
 
-The `jsonFormatX` methods come from [Spray 
Json](https://github.com/spray/spray-json). The `X` represents the number of 
parameters in the underlying case classes:
+The `jsonFormatX` methods come from Spray JSON. The `X` represents the number 
of parameters in the underlying case classes:
 
 @@snip 
[UserRegistryActor.scala]($g8src$/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala)
 { #user-case-classes }
 
-We won't go into how the formatters are implemented. All you need to remember 
for now is to define the formatters as implicit and that the formatter used 
should map the number of parameters belonging to the case class it converts.
-
-Comment: I was a bit confused by the previous paragraph. Does the user have to 
write their own formatters or are these available as libraries?
+We won't go into how the formatters are implemented - this is done for us by 
the library. All you need to remember for now is to define the formatters as 
implicit and that the formatter used should map the number of parameters 
belonging to the case class it converts.
+
+@@@ note
+  
+    While we used Spray JSON in this example, various other libraries are 
supported via the [Akka HTTP 
JSON](https://github.com/hseeberger/akka-http-json) 
+    project, including [Jackson](https://github.com/FasterXML/jackson), [Play 
JSON](https://www.playframework.com/documentation/2.6.x/ScalaJson) 
+    or [circe](https://circe.github.io/circe/).
+    
+    Each library comes with different trade-offs in performance and 
user-friendlieness. Spray JSON is generally the fastest, though it requires you 
to write the format
+    values explicitly. If you'd rather make "everything" automatically 
marshallable into JSON values you might want to use Jackson or Circe instead. 
+    
+    If you're not sure, we recommend sticking to Spray JSON as it's the 
closest in philosophy to Akka HTTP - being explicit about all capabilities.
+  
+@@@ 
 
 Now that we've examined the example app thoroughly, let's test a few the 
remaining use cases.
+
diff --git a/docs/src/main/paradox/running-the-application.md 
b/docs/src/main/paradox/running-the-application.md
index d58681f..d5fe205 100644
--- a/docs/src/main/paradox/running-the-application.md
+++ b/docs/src/main/paradox/running-the-application.md
@@ -1,5 +1,5 @@
-Final testing
--------------
+Running the application
+-----------------------
 
 When you ran the example for the first time, you were able to create and 
retrieve multiple users. Now that you understand how the example is 
implemented, let's confirm that the rest of the functionality works. We want to 
verify that:
 
@@ -9,21 +9,21 @@ When you ran the example for the first time, you were able to 
create and retriev
 
 To test this functionality, follow these steps. If you need reminders on 
starting the app or sending requests, refer to the 
@ref:[instructions](index.md#exercising-the-example) in the beginning.
 
-1. If the Akka HTTP server is still running, stop and restart it.
-2. With no users registered, use your tool of choice to:
-3. Retrieve a list of users. Hint: use the `GET` method and append `/users` to 
the URL.
+`1.` If the Akka HTTP server is still running, stop and restart it.
+`2.` With no users registered, use your tool of choice to:
+`3.` Retrieve a list of users. Hint: use the `GET` method and append `/users` 
to the URL.
 
 You should get back an empty list: `{"users":[]}`
 
-4. Try to retrieve a single user named `MrX`. Hint: use the `GET` method and 
append `users/MrX` to the URL.
+`4.` Try to retrieve a single user named `MrX`. Hint: use the `GET` method and 
append `users/MrX` to the URL.
 
 You should get back the message: `User MrX is not registered.`
 
-5. Try adding one or more users. Hint: use the `POST` method, append `/users` 
to the URL, and format the data in JSON, similar to: 
`{"name":"MrX","age":31,"countryOfResidence":"Canada"}`
+`5.` Try adding one or more users. Hint: use the `POST` method, append 
`/users` to the URL, and format the data in JSON, similar to: 
`{"name":"MrX","age":31,"countryOfResidence":"Canada"}`
 
 You should get back the message: `User MrX created.`
 
-6. Try deleting a user you just added. Hint: use the `DELETE`, and append 
`/users/<NAME>` to the URL.
+`6.` Try deleting a user you just added. Hint: use the `DELETE`, and append 
`/users/<NAME>` to the URL.
 
 You should get back the message: `User MrX deleted.`
 
diff --git a/docs/src/main/paradox/testing-routes.md 
b/docs/src/main/paradox/testing-routes.md
new file mode 100644
index 0000000..4151e63
--- /dev/null
+++ b/docs/src/main/paradox/testing-routes.md
@@ -0,0 +1,104 @@
+Testing routes
+--------------
+
+If you remember when we started out with our `QuickstartServer`, we decided to 
put the routes themselves into a separate 
+trait. Back there we said that we're doing this to eparate the infrastructure 
code (setting up the actor system and 
+wiring up all the dependencies and actors), from the routes, which should only 
declare what they need to work with,
+and can therefore be a bit more focused on their task at hand. This of course 
leads us to better testability.
+
+This separation, other than being a good idea on its own, was all for this 
moment! For when we want to write tests
+to cover all our routes, without having to bring up the entire application. 
+
+## Unit testing routes
+
+There are multiple ways one can test an HTTP application of course, however 
lets start at the simplest and also quickest 
+way: unit testing. In this style of testing, we won't even need to spin up an 
actual server - all the tests will be 
+executed on the routes directly - without the need of hitting actual network. 
This is due to Akka HTTP's pure design
+and separation between the network layer (represented as a bi-directional 
`Flow` of byte strings to Http domain objects).
+
+In other words, unit testing in Akka HTTP is simply "executing" the routes by 
passing in an `HttpResponse` to the route,
+and later inspecting what `HttpResponse` (or `rejection` if the request could 
not be handled) it resulted in. All this 
+in-memory, without having to start a real HTTP server - which gives us supreme 
speed and turn-over time when developing
+an application using Akka.
+
+First we'll need to extend a number of base traits:
+
+@@snip 
[QuickstartServer.scala]($g8srctest$/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala)
 { #test-top }
+
+Here we're using ScalaTest which provides the testing *style* `WordSpec` and 
the `Matchers` trait which provides
+the `something should === (somethingElse)` syntax [and 
more](http://www.scalatest.org/user_guide/using_matchers). 
+Next we inherit the Akka HTTP provided `ScalatestRouteTest` bridge trait that 
provides Route specific testing facilities, 
+and binds into ScalaTest's lifecycle methods such that the `ActorSystem` is 
started and stopped automatically for us.
+
+
+@@@ note
+
+If you're using Specs2 instead, you can simply extend the `Specs2RouteTest` 
support trait instead.
+
+@@@ 
+
+
+Next we'll need to bring into the test class our routes that we want to test. 
We're doing this by extending the `UserRoutes` trait in the spec itself - this 
allows us to bring all marshallers into scope for the tests to use, as well as 
makes it possible to implement all abstract members of that trait in the test 
itself - all in in a fully type-safe way.
+
+We'll need to provide it with an `ActorSystem`, which is done by the fact that 
the `ScalatestRouteTest` trait 
+already provides a field called `system: ActorSystem`. Next we need to 
implement the `userRegistryActor: ActorRef` that the routes are interacting 
with we'll create a TestProbe instead - which will allow us to verify the route 
indeed did send a message do the Actor or not etc. 
+
+@@snip 
[QuickstartServer.scala]($g8srctest$/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala)
 { #set-up }
+
+We could create an actor that replies with a mocked response here instead if 
we wanted to, this is especially useful if
+the route awaits an response from the actor before rendering the 
`HttpResponse` to the client. Read about the [Akka TestKit 
](http://doc.akka.io/docs/akka/current/scala/testing.html) and it's utilities 
like `TestProbe` if this is something you'd like to learn more about. 
+
+Let's write our first test, in which we'll hit the `/users` endpoint with a 
`GET` request:
+
+@@snip 
[QuickstartServer.scala]($g8srctest$/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala)
 { #actual-test }
+
+We simply construct a raw `HttpRequest` object and pass it into the route 
using the `~>` testing operator provided by `ScalatestRouteTest`. Next we do 
the same and pipe the result of that route into a check block, so the full 
syntax is: 
+`request ~> route ~> check { }`. This syntax allows us to not worry about the 
asynchronous nature of the request handling.
+After all, the route is a function of `HttpRequest => Future[HttpResponse]` - 
here we don't need to explicitly write code
+that's awaiting on the response, it's handled for us.
+
+Inside the check block we can inspect all kinds of attributes of the received 
response, like `status`, `contentType` and 
+of course the full response which we can easily convert to a string for 
testing using `responseAs[String]`. This infrastructure
+is using the same marshalling infrastructure as our routes, so if the response 
was a `User` JSON, we could say `responseAs[User]` and write our assertions on 
the actual object.
+
+In the next test we'd like test a `POST` endpoint, so we need to send an 
entity to the endpoint in order to create a new `User`. This time, instead of 
using the raw `HttpRequest` to build the request we'll use a small DSL provided 
by the Akka HTTP. The DSL allows you to write `Post("/hello)` instead of having 
to declare the full thing in the raw API (which would have been: 
`HttpRequest(method = HttpMethods.POST, uri = "/hello")`), and next we'll add 
the User JSON into the request body: 
+
+@@snip 
[QuickstartServer.scala]($g8srctest$/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala)
 { #testing-post }
+
+So in order to add the entity we've used the `Marshal(object).to[TargetType]` 
syntax, which uses the same marshalling
+infrastructure that is used when we `complete(object)`. Since we extend the 
`UserRoutes` trait in this test, all the 
+necessary implicits for the marshalling to work this way are also present in 
scope of the test. This is another reason
+why it's so convenient to extend the Routes trait when testing it - everything 
the actual code was using, we also have at
+our disposal when writing the test.
+
+This concludes the basics of unit testing HTTP routes, to learn more please 
refer to the 
+[Akka HTTP TestKit documentation]().
+
+### Complete unit unit test code listing
+
+For reference, here's the entire unit test code:
+
+@@snip 
[QuickstartServer.scala]($g8srctest$/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala)
+
+
+## A note Integration testing routes
+
+While definitions of "what a pure unit-test is" are sometimes a subject of 
fierce debates in programming communities,
+we refer to the above testing style as "route unit testing" since it's light 
weight and allows to test the routes in 
+isolation, especially if their dependencies would be mocked our with test 
stubs, instead of hitting real APIs.
+
+Sometimes however one wants to test the complete "full application", including 
starting a real HTTP server
+
+@@@ warning
+  
+  Some network specific features like timeouts, behaviour of entities 
(streamed directly from the network, instead of 
+  in memory objects like in the unit testing style) may behave differently in 
the unit-testing style showcased above.
+  
+  If you want to test specific timing and entity draining behaviours of your 
apps you may want to add full integration tests for them. For most routes this 
should not be needed, however we'd recommend doing so when using more of the 
streaming features of Akka HTTP.
+  
+@@@
+
+Usually such tests would be implemented by starting the application the same 
way as we started it in the `QuickstartServer`,
+in `beforeAll` (in ScalaTest), then hitting the API with http requests using 
the HTTP Client and asserting on the responses,
+finally shutting down the server in `afterAll` (in ScalaTest).
+
diff --git a/src/main/g8/.gitignore b/src/main/g8/.gitignore
new file mode 100644
index 0000000..818019b
--- /dev/null
+++ b/src/main/g8/.gitignore
@@ -0,0 +1,2 @@
+*.class
+.idea
diff --git a/src/main/g8/build.sbt b/src/main/g8/build.sbt
index c286488..4af7219 100644
--- a/src/main/g8/build.sbt
+++ b/src/main/g8/build.sbt
@@ -11,9 +11,9 @@ lazy val root = (project in file(".")).
     libraryDependencies ++= Seq(
       "com.typesafe.akka" %% "akka-http"            % akkaHttpVersion,
       "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
+      "com.typesafe.akka" %% "akka-http-xml"        % akkaHttpVersion,
       "com.typesafe.akka" %% "akka-stream"          % akkaVersion,
-      "com.typesafe.akka" %% "akka-http-xml"     % akkaHttpVersion,
-      "com.typesafe.akka" %% "akka-stream"       % akkaVersion,
+      
       "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
       "org.scalatest"     %% "scalatest"         % "3.0.1"         % Test
     )
diff --git 
a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala 
b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala
index ffebd52..e9208e7 100644
--- 
a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala
+++ 
b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala
@@ -1,10 +1,14 @@
 package com.lightbend.akka.http.sample
 
 import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
-import spray.json.DefaultJsonProtocol
+import com.lightbend.akka.http.sample.UserRegistryActor.ActionPerformed
+import spray.json.{ DefaultJsonProtocol, RootJsonFormat }
 
 trait JsonSupport extends SprayJsonSupport {
-  import DefaultJsonProtocol._
+  import DefaultJsonProtocol._ // import the default encoders for primitive 
types (Int, String, Lists etc)
+
   implicit val userJsonFormat = jsonFormat3(User)
   implicit val usersJsonFormat = jsonFormat1(Users)
+
+  implicit val actionPerformedJsonFormat = jsonFormat1(ActionPerformed)
 }
diff --git 
a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala
 
b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala
index 45b5579..948cd11 100644
--- 
a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala
+++ 
b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala
@@ -1,30 +1,21 @@
 package com.lightbend.akka.http.sample
 
 import akka.actor.{ ActorRef, ActorSystem }
-import akka.pattern.ask
-import akka.util.Timeout
-
-import scala.concurrent.duration._
-import akka.stream.ActorMaterializer
 import akka.http.scaladsl.Http
-import akka.http.scaladsl.server.Directives._
 import akka.http.scaladsl.Http.ServerBinding
-import akka.http.scaladsl.model.StatusCodes
-import akka.http.scaladsl.server.{ ExceptionHandler, Route }
-import akka.http.scaladsl.server.directives.MethodDirectives.delete
-import akka.http.scaladsl.server.directives.MethodDirectives.get
-import akka.http.scaladsl.server.directives.MethodDirectives.post
-import akka.http.scaladsl.server.directives.RouteDirectives.complete
-import akka.http.scaladsl.server.directives.PathDirectives.path
+import akka.http.scaladsl.server.Route
+import akka.stream.ActorMaterializer
+import akka.util.Timeout
 
-import scala.concurrent.ExecutionContext
-import scala.concurrent.Future
+import scala.concurrent.{ ExecutionContext, Future }
+import scala.concurrent.duration._
 import scala.io.StdIn
-import scala.util.{ Failure, Success }
-import com.lightbend.akka.http.sample.UserRegistryActor._
 
 //#main-class
-object QuickstartServer extends App with JsonSupport {
+object QuickstartServer extends App
+    with UserRoutes {
+
+  // set up ActorSystem and other dependencies here
   //#main-class
   //#server-bootstrapping
   implicit val system: ActorSystem = ActorSystem("helloAkkaHttpServer")
@@ -36,56 +27,10 @@ object QuickstartServer extends App with JsonSupport {
 
   val userRegistryActor: ActorRef = system.actorOf(UserRegistryActor.props, 
"userRegistryActor")
 
-  // Required by the `ask` (?) method below
-  implicit val timeout = Timeout(5 seconds)
-
-  //#all-routes
+  //#main-class
   lazy val routes: Route =
-    //#users-get-post
-    //#users-get-delete
-    pathPrefix("users") {
-      //#users-get-delete
-      pathEnd {
-        get {
-          val users: Future[Users] = (userRegistryActor ? 
GetUsers).mapTo[Users]
-          complete(users)
-        } ~
-          post {
-            entity(as[User]) { user =>
-              val userCreated: Future[ActionPerformed] = (userRegistryActor ? 
CreateUser(user)).mapTo[ActionPerformed]
-              onComplete(userCreated) { r =>
-                r match {
-                  case Success(ActionPerformed(description)) => 
complete((StatusCodes.Created, description))
-                  case Failure(ex) => 
complete((StatusCodes.InternalServerError, ex))
-                }
-              }
-            }
-          }
-      } ~
-        //#users-get-post
-        //#users-get-delete
-        path(Segment) { name =>
-          get {
-            //#retrieve-user-info
-            val maybeUser: Future[Option[User]] = (userRegistryActor ? 
GetUser(name)).mapTo[Option[User]]
-            rejectEmptyResponse {
-              complete(maybeUser)
-            }
-            //#retrieve-user-info
-          } ~
-            delete {
-              val userDeleted: Future[ActionPerformed] = (userRegistryActor ? 
DeleteUser(name)).mapTo[ActionPerformed]
-              onComplete(userDeleted) { r =>
-                r match {
-                  case Success(ActionPerformed(description)) => 
complete((StatusCodes.OK, description))
-                  case Failure(ex) => 
complete((StatusCodes.InternalServerError, ex))
-                }
-              }
-            }
-        }
-      //#users-get-delete
-    }
-  //#all-routes
+    userRoutes // from the UserRoutes trait
+  //#main-class
 
   //#http-server
   val serverBindingFuture: Future[ServerBinding] = 
Http().bindAndHandle(routes, "localhost", 8080)
@@ -93,7 +38,10 @@ object QuickstartServer extends App with JsonSupport {
   StdIn.readLine()
   serverBindingFuture
     .flatMap(_.unbind())
-    .onComplete(_ => system.terminate())
+    .onComplete { done =>
+      done.failed.map { ex => log.error(ex, "Failed unbinding") }
+      system.terminate() 
+    }
   //#http-server
   //#main-class
 }
diff --git 
a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala
 
b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala
index 7a910ac..7a667eb 100644
--- 
a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala
+++ 
b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala
@@ -1,11 +1,10 @@
 package com.lightbend.akka.http.sample
 
 import akka.actor.{ Actor, ActorLogging, Props }
-import scala.collection.mutable.Set
 
 //#user-case-classes
-case class User(name: String, age: Int, countryOfResidence: String)
-case class Users(users: Seq[User])
+final case class User(name: String, age: Int, countryOfResidence: String)
+final case class Users(users: Seq[User])
 //#user-case-classes
 
 object UserRegistryActor {
@@ -21,18 +20,18 @@ object UserRegistryActor {
 class UserRegistryActor extends Actor with ActorLogging {
   import UserRegistryActor._
 
-  val users: Set[User] = Set.empty[User]
+  var users = Set.empty[User]
 
   def receive = {
     case GetUsers =>
-      sender ! Users(users.toSeq)
+      sender() ! Users(users.toSeq)
     case CreateUser(user) =>
       users += user
-      sender ! ActionPerformed(s"User ${user.name} created.")
+      sender() ! ActionPerformed(s"User ${user.name} created.")
     case GetUser(name) =>
-      sender ! users.find(_.name == name)
+      sender() ! users.find(_.name == name)
     case DeleteUser(name) =>
-      users.find(_.name == name) map { user => users -= user }
-      sender ! ActionPerformed(s"User ${name} deleted.")
+      users.find(_.name == name) foreach { user => users -= user }
+      sender() ! ActionPerformed(s"User ${name} deleted.")
   }
 }
diff --git 
a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRoutes.scala 
b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRoutes.scala
new file mode 100644
index 0000000..1168a7d
--- /dev/null
+++ b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRoutes.scala
@@ -0,0 +1,96 @@
+package com.lightbend.akka.http.sample
+
+import akka.actor.{ ActorRef, ActorSystem }
+import akka.event.Logging
+
+import scala.concurrent.duration._
+import akka.stream.ActorMaterializer
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.Http.ServerBinding
+import akka.http.scaladsl.model.StatusCodes
+import akka.http.scaladsl.server.{ ExceptionHandler, Route }
+import akka.http.scaladsl.server.directives.MethodDirectives.delete
+import akka.http.scaladsl.server.directives.MethodDirectives.get
+import akka.http.scaladsl.server.directives.MethodDirectives.post
+import akka.http.scaladsl.server.directives.RouteDirectives.complete
+import akka.http.scaladsl.server.directives.PathDirectives.path
+
+import scala.concurrent.ExecutionContext
+import scala.concurrent.Future
+import scala.util.{ Failure, Success }
+import com.lightbend.akka.http.sample.UserRegistryActor._
+import akka.pattern.ask
+import akka.util.Timeout
+
+//#user-routes-class
+trait UserRoutes extends JsonSupport {
+  //#user-routes-class
+
+  // we leave these abstract, since they will be provided by the App
+  implicit def system: ActorSystem
+
+  lazy val log = Logging(system, classOf[UserRoutes])
+
+  // other dependencies that UserRoutes use
+  def userRegistryActor: ActorRef
+
+  // Required by the `ask` (?) method below
+  implicit lazy val timeout = Timeout(5.seconds) // usually we'd obtain the 
timeout from the system's configuration
+
+  //#all-routes
+  //#users-get-post
+  //#users-get-delete   
+  lazy val userRoutes =
+    pathPrefix("users") {
+      concat(
+        //#users-get-delete
+        pathEnd {
+          concat(
+            get {
+              val users: Future[Users] = 
+                (userRegistryActor ? GetUsers).mapTo[Users]
+              complete(users)
+            },
+            post {
+              entity(as[User]) { user =>
+                val userCreated: Future[ActionPerformed] = 
+                  (userRegistryActor ? CreateUser(user)).mapTo[ActionPerformed]
+                onSuccess(userCreated) { performed => 
+                  log.info("Created user [{}]: {}", user.name, 
performed.description)
+                  complete((StatusCodes.Created, performed))
+                }
+              }
+            }
+          )
+        },
+        //#users-get-post
+        //#users-get-delete
+        path(Segment) { name =>
+          concat(
+            get {
+              //#retrieve-user-info
+              val maybeUser: Future[Option[User]] = 
+                (userRegistryActor ? GetUser(name)).mapTo[Option[User]]
+              rejectEmptyResponse {
+                complete(maybeUser)
+              }
+              //#retrieve-user-info
+            },
+            delete {
+              //#users-delete-logic
+              val userDeleted: Future[ActionPerformed] = 
+                (userRegistryActor ? DeleteUser(name)).mapTo[ActionPerformed]
+              onSuccess(userDeleted) { performed =>
+                log.info("Deleted user [{}]: {}", name, performed.description)
+                complete((StatusCodes.OK, performed))
+              }
+              //#users-delete-logic
+            }
+          )
+        }
+      )
+      //#users-get-delete
+    }
+  //#all-routes
+}
diff --git 
a/src/main/g8/src/test/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala
 
b/src/main/g8/src/test/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala
new file mode 100644
index 0000000..e2b1234
--- /dev/null
+++ 
b/src/main/g8/src/test/scala/com/lightbend/akka/http/sample/UserRoutesSpec.scala
@@ -0,0 +1,86 @@
+package com.lightbend.akka.http.sample
+
+//#test-top
+import akka.actor.ActorRef
+import akka.http.javadsl.server.Rejections
+import akka.http.scaladsl.marshalling.Marshal
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.server.SchemeRejection
+import akka.http.scaladsl.testkit.ScalatestRouteTest
+import org.scalatest.concurrent.ScalaFutures
+import org.scalatest.{ Matchers, WordSpec }
+
+//#set-up
+class UserRoutesSpec extends WordSpec with Matchers with ScalaFutures with 
ScalatestRouteTest
+    with UserRoutes {
+  //#test-top
+
+  // Here we need to implement all the abstract members of UserRoutes.
+  // We use the real UserRegistryActor to test it while we hit the Routes, 
+  // but we could "mock" it by implementing it in-place or by using a 
TestProbe() 
+  override val userRegistryActor: ActorRef =
+    system.actorOf(UserRegistryActor.props, "userRegistry")
+
+  lazy val routes = userRoutes
+
+  //#set-up
+
+  //#actual-test
+  "UserRoutes" should {
+    "return no users if no present (GET /users)" in {
+      // note that there's no need for the host part in the uri:
+      val request = HttpRequest(uri = "/users")
+
+      request ~> routes ~> check {
+        status should ===(StatusCodes.OK)
+
+        // we expect the response to be json:
+        contentType should ===(ContentTypes.`application/json`)
+
+        // and no entries should be in the list:
+        entityAs[String] should ===("""{"users":[]}""")
+      }
+    }
+    //#actual-test
+
+    //#testing-post
+    "be able to add users (POST /users)" in {
+      val user = User("Kapi", 42, "jp")
+      val userEntity = Marshal(user).to[MessageEntity].futureValue // 
futureValue is from ScalaFutures
+
+      // using the RequestBuilding DSL:
+      val request = Post("/users").withEntity(userEntity)
+
+      request ~> routes ~> check {
+        status should ===(StatusCodes.Created)
+
+        // we expect the response to be json:
+        contentType should ===(ContentTypes.`application/json`)
+
+        // and we know what message we're expecting back:
+        entityAs[String] should ===("""{"description":"User Kapi created."}""")
+      }
+    }
+    //#testing-post
+
+    "be able to remove users (DELETE /users)" in {
+      // user the RequestBuilding DSL provided by ScalatestRouteSpec:
+      val request = Delete(uri = "/users/Kapi")
+
+      request ~> routes ~> check {
+        status should ===(StatusCodes.OK)
+
+        // we expect the response to be json:
+        contentType should ===(ContentTypes.`application/json`)
+
+        // and no entries should be in the list:
+        entityAs[String] should ===("""{"description":"User Kapi deleted."}""")
+      }
+    }
+    //#actual-test
+  }
+  //#actual-test
+
+  //#set-up
+}
+//#set-up


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to