Update the testing guide for 0.3.

This commit is contained in:
Sergio Benitez 2017-07-04 19:21:46 -07:00
parent 05a8a93eec
commit b2ab3861b4
2 changed files with 145 additions and 70 deletions

View File

@ -21,8 +21,10 @@ aspect of Rocket. The sections are:
parsing, and validating. parsing, and validating.
- **[Responses](responses/):** discusses generating responses. - **[Responses](responses/):** discusses generating responses.
- **[State](state/):** how to manage state in a Rocket application. - **[State](state/):** how to manage state in a Rocket application.
- **[Fairings](fairings/):** introduction to Rocket's structure middleware.
- **[Testing](testing/):** how to unit and integration test a Rocket - **[Testing](testing/):** how to unit and integration test a Rocket
application. application.
- **[Configuration](configuration/):** how to configure a Rocket application.
- **[Pastebin](pastebin/):** a tutorial on how to create a pastebin with - **[Pastebin](pastebin/):** a tutorial on how to create a pastebin with
Rocket. Rocket.
- **[Conclusion](conclusion/):** concludes the guide and discusses next steps - **[Conclusion](conclusion/):** concludes the guide and discusses next steps

View File

@ -1,59 +1,118 @@
# Testing # Testing
Every application should be well tested. Rocket provides the tools to perform Every application should be well tested and understandable. Rocket provides the
unit and integration tests on your application as well as inspect Rocket tools to perform unit and integration tests. It also provides a means to inspect
generated code. code generated by Rocket.
## Tests ## Local Dispatching
Rocket includes a built-in [testing](https://api.rocket.rs/rocket/testing/) Rocket applications are tested by dispatching requests to a local instance of
module that allows you to unit and integration test your Rocket applications. `Rocket`. The [`local`] module contains all of the structures necessary to do
Testing is simple: so. In particular, it contains a [`Client`] structure that is used to create
[`LocalRequest`] structures that can be dispatched against a given [`Rocket`]
instance. Usage is straightforward:
1. Construct a `Rocket` instance. 1. Construct a `Rocket` instance that represents the application.
2. Construct a `MockRequest`.
3. Dispatch the request using the `Rocket` instance.
4. Inspect, validate, and verify the `Response`.
After setting up, we'll walk through each of these steps for the "Hello, world!" ```rust
program below: let rocket = rocket::ignite();
```
2. Construct a `Client` using the `Rocket` instance.
```rust
let client = Client::new(rocket).expect("valid rocket instance");
```
3. Construct requests using the `Client` instance.
```rust
let req = client.get("/");
```
3. Dispatch the request to retrieve the response.
```rust
let response = req.dispatch();
```
[`local`]: https://api.rocket.rs/rocket/local/index.html
[`Client`]: https://api.rocket.rs/rocket/local/struct.Client.html
[`LocalRequest`]: https://api.rocket.rs/rocket/local/struct.LocalRequest.html
[`Rocket`]: https://api.rocket.rs/rocket/struct.Rocket.html
## Validating Responses
A `dispatch` of a `LocalRequest` returns a [`LocalResponse`] which can be used
transparently as a [`Response`] value. During testing, the response is usually
validated against expected properties. These includes things like the response
HTTP status, the inclusion of headers, and expected body data.
The [`Response`] type provides methods to ease this sort of validation. We list
a few below:
* [`status`]: returns the HTTP status in the response.
* [`content_type`]: returns the Content-Type header in the response.
* [`headers`]: returns a map of all of the headers in the response.
* [`body_string`]: returns the body data as a `String`.
* [`body_bytes`]: returns the body data as a `Vec<u8>`.
[`LocalResponse`]: https://api.rocket.rs/rocket/local/struct.LocalResponse.html
[`Response`]: https://api.rocket.rs/rocket/struct.Response.html
[`status`]: https://api.rocket.rs/rocket/struct.Response.html#method.status
[`content_type`]: https://api.rocket.rs/rocket/struct.Response.html#method.content_type
[`headers`]: https://api.rocket.rs/rocket/struct.Response.html#method.headers
[`body_string`]: https://api.rocket.rs/rocket/struct.Response.html#method.body_string
[`body_bytes`]: https://api.rocket.rs/rocket/struct.Response.html#method.body_bytes
These methods are typically used in combination with the `assert_eq!` or
`assert!` macros as follows:
```rust ```rust
#![feature(plugin)] let rocket = rocket::ignite();
#![plugin(rocket_codegen)] let client = Client::new(rocket).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
extern crate rocket; assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
assert!(response.headers().get_one("X-Special").is_some());
assert_eq!(response.body_string(), Some("Expected Body.".into()));
```
## Testing "Hello, world!"
To solidify an intuition for how Rocket applications are tested, we walk through
how to test the "Hello, world!" application below:
```rust
#[get("/")] #[get("/")]
fn hello() -> &'static str { fn hello() -> &'static str {
"Hello, world!" "Hello, world!"
} }
fn rocket() -> Rocket {
rocket::ignite().mount("/", routes![hello])
}
fn main() {
rocket().launch();
}
``` ```
Notice that we've separated the _creation_ of the `Rocket` instance from the
_launch_ of the instance. As you'll soon see, this makes testing our application
easier, less verbose, and less error-prone.
### Setting Up ### Setting Up
For the `testing` module to be available, Rocket needs to be compiled with the First, we'll create a `test` module with the proper imports:
_testing_ feature enabled. Since this feature should only be enabled when your
application is compiled for testing, the recommended way to enable the _testing_
feature is via Cargo's `[dev-dependencies]` section in the `Cargo.toml` file as
follows:
```toml
[dev-dependencies]
rocket = { version = "0.2.8", features = ["testing"] }
```
With this in place, running `cargo test` will result in Cargo compiling Rocket
with the _testing_ feature, thus enabling the `testing` module.
You'll also need a `test` module with the proper imports:
```rust ```rust
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::rocket; use super::rocket;
use rocket::testing::MockRequest; use rocket::local::Client;
use rocket::http::{Status, Method}; use rocket::http::Status;
#[test] #[test]
fn hello_world() { fn hello_world() {
@ -62,75 +121,89 @@ mod test {
} }
``` ```
In the remainder of this section, we'll work on filling in the `hello_world` You can also move the body of the `test` module into its own file, say
testing function to ensure that the `hello` route results in a `Response` with `tests.rs`, and then import the module into the main file using:
_"Hello, world!"_ in the body.
```rust
#[cfg(test)] mod tests;
```
### Testing ### Testing
We'll begin by constructing a `Rocket` instance with the `hello` route mounted To test our "Hello, world!" application, we first create a `Client` for our
at the root path. We do this in the same way we would normally with one `Rocket` instance. It's okay to use methods like `expect` and `unwrap` during
exception: we need to refer to the `testing` route in the `super` namespace: testing: we _want_ our tests to panic when something goes wrong.
```rust ```rust
let rocket = rocket::ignite().mount("/", routes![super::hello]); let client = Client::new(rocket()).expect("valid rocket instance");
``` ```
Next, we create a `MockRequest` that issues a `Get` request to the `"/"` path: Then, we create a new `GET /` request and dispatch it, getting back our
application's response:
```rust ```rust
let mut req = MockRequest::new(Method::Get, "/"); let mut response = client.get("/").dispatch();
``` ```
We now ask Rocket to perform a full dispatch, which includes routing, Finally, we ensure that the response contains the information we expect it to.
pre-processing and post-processing, and retrieve the `Response`: Here, we want to ensure two things:
```rust
let mut response = req.dispatch_with(&rocket);
```
Finally, we can test the
[Response](https://api.rocket.rs/rocket/struct.Response.html) values to ensure
that it contains the information we expect it to. We want to ensure two things:
1. The status is `200 OK`. 1. The status is `200 OK`.
2. The body is the string "Hello, world!". 2. The body is the string "Hello, world!".
We do this by querying the `Response` object directly: We do this by checking the `Response` object directly:
```rust ```rust
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some("Hello, world!".into()));
let body_str = response.body().and_then(|b| b.into_string());
assert_eq!(body_str, Some("Hello, world!".to_string()));
``` ```
That's it! Run the tests with `cargo test`. The complete application, with That's it! Altogether, this looks like:
testing, can be found in the [GitHub testing
example](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/testing). ```rust
#[cfg(test)]
mod test {
use super::rocket;
use rocket::local::Client;
use rocket::http::Status;
#[test]
fn hello_world() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some("Hello, world!".into()));
}
}
```
The tests can be run with `cargo test`. You can find the full source code to
[this example on
GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/testing).
## Codegen Debug ## Codegen Debug
It is sometimes useful to inspect the code that Rocket's code generation is It can be useful to inspect the code that Rocket's code generation is emitting,
emitting, especially when you get a strange type error. To have Rocket log the especially when you get a strange type error. To have Rocket log the code that
code that it is emitting to the console, set the `ROCKET_CODEGEN_DEBUG` it is emitting to the console, set the `ROCKET_CODEGEN_DEBUG` environment
environment variable when compiling: variable when compiling:
```rust ```rust
ROCKET_CODEGEN_DEBUG=1 cargo build ROCKET_CODEGEN_DEBUG=1 cargo build
``` ```
During compilation, you should see output like this: During compilation, you should see output like:
```rust ```rust
Emitting item: Emitting item:
fn rocket_route_fn_hello<'_b>(_req: &'_b ::rocket::Request, fn rocket_route_fn_hello<'_b>(
_data: ::rocket::Data) __req: &'_b ::rocket::Request,
-> ::rocket::handler::Outcome<'_b> { __data: ::rocket::Data
) -> ::rocket::handler::Outcome<'_b> {
let responder = hello(); let responder = hello();
::rocket::handler::Outcome::from(_req, responder) ::rocket::handler::Outcome::from(__req, responder)
} }
``` ```
This corresponds to the facade request handler Rocket generated for the `hello` This corresponds to the facade request handler Rocket has generated for the
route. `hello` route.