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.
- **[Responses](responses/):** discusses generating responses.
- **[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
application.
- **[Configuration](configuration/):** how to configure a Rocket application.
- **[Pastebin](pastebin/):** a tutorial on how to create a pastebin with
Rocket.
- **[Conclusion](conclusion/):** concludes the guide and discusses next steps

View File

@ -1,59 +1,118 @@
# Testing
Every application should be well tested. Rocket provides the tools to perform
unit and integration tests on your application as well as inspect Rocket
generated code.
Every application should be well tested and understandable. Rocket provides the
tools to perform unit and integration tests. It also provides a means to inspect
code generated by Rocket.
## Tests
## Local Dispatching
Rocket includes a built-in [testing](https://api.rocket.rs/rocket/testing/)
module that allows you to unit and integration test your Rocket applications.
Testing is simple:
Rocket applications are tested by dispatching requests to a local instance of
`Rocket`. The [`local`] module contains all of the structures necessary to do
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.
2. Construct a `MockRequest`.
3. Dispatch the request using the `Rocket` instance.
4. Inspect, validate, and verify the `Response`.
1. Construct a `Rocket` instance that represents the application.
After setting up, we'll walk through each of these steps for the "Hello, world!"
program below:
```rust
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
#![feature(plugin)]
#![plugin(rocket_codegen)]
let rocket = rocket::ignite();
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("/")]
fn hello() -> &'static str {
"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
For the `testing` module to be available, Rocket needs to be compiled with the
_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:
First, we'll create a `test` module with the proper imports:
```rust
#[cfg(test)]
mod test {
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::{Status, Method};
use rocket::local::Client;
use rocket::http::Status;
#[test]
fn hello_world() {
@ -62,75 +121,89 @@ mod test {
}
```
In the remainder of this section, we'll work on filling in the `hello_world`
testing function to ensure that the `hello` route results in a `Response` with
_"Hello, world!"_ in the body.
You can also move the body of the `test` module into its own file, say
`tests.rs`, and then import the module into the main file using:
```rust
#[cfg(test)] mod tests;
```
### Testing
We'll begin by constructing a `Rocket` instance with the `hello` route mounted
at the root path. We do this in the same way we would normally with one
exception: we need to refer to the `testing` route in the `super` namespace:
To test our "Hello, world!" application, we first create a `Client` for our
`Rocket` instance. It's okay to use methods like `expect` and `unwrap` during
testing: we _want_ our tests to panic when something goes wrong.
```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
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,
pre-processing and post-processing, and retrieve the `Response`:
```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:
Finally, we ensure that the response contains the information we expect it to.
Here, we want to ensure two things:
1. The status is `200 OK`.
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
assert_eq!(response.status(), Status::Ok);
let body_str = response.body().and_then(|b| b.into_string());
assert_eq!(body_str, Some("Hello, world!".to_string()));
assert_eq!(response.body_string(), Some("Hello, world!".into()));
```
That's it! Run the tests with `cargo test`. The complete application, with
testing, can be found in the [GitHub testing
example](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/testing).
That's it! Altogether, this looks like:
```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
It is sometimes useful to inspect the code that Rocket's code generation is
emitting, especially when you get a strange type error. To have Rocket log the
code that it is emitting to the console, set the `ROCKET_CODEGEN_DEBUG`
environment variable when compiling:
It can be useful to inspect the code that Rocket's code generation is emitting,
especially when you get a strange type error. To have Rocket log the code that
it is emitting to the console, set the `ROCKET_CODEGEN_DEBUG` environment
variable when compiling:
```rust
ROCKET_CODEGEN_DEBUG=1 cargo build
```
During compilation, you should see output like this:
During compilation, you should see output like:
```rust
Emitting item:
fn rocket_route_fn_hello<'_b>(_req: &'_b ::rocket::Request,
_data: ::rocket::Data)
-> ::rocket::handler::Outcome<'_b> {
fn rocket_route_fn_hello<'_b>(
__req: &'_b ::rocket::Request,
__data: ::rocket::Data
) -> ::rocket::handler::Outcome<'_b> {
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`
route.
This corresponds to the facade request handler Rocket has generated for the
`hello` route.