Rocket/site/guide/8-testing.md

304 lines
8.7 KiB
Markdown
Raw Normal View History

# Testing
2017-07-05 02:21:46 +00:00
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.
2017-07-05 02:21:46 +00:00
## Local Dispatching
2017-07-05 02:21:46 +00:00
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:
2017-07-05 02:21:46 +00:00
1. Construct a `Rocket` instance that represents the application.
```rust
let rocket = rocket::ignite();
# let _ = rocket;
```
2017-07-05 02:21:46 +00:00
2. Construct a `Client` using the `Rocket` instance.
```rust
# use rocket::local::Client;
# let rocket = rocket::ignite();
let client = Client::new(rocket).expect("valid rocket instance");
# let _ = client;
```
2017-07-05 02:21:46 +00:00
3. Construct requests using the `Client` instance.
```rust
# use rocket::local::Client;
# let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap();
let req = client.get("/");
# let _ = req;
```
2017-07-05 02:21:46 +00:00
2017-07-10 11:59:55 +00:00
4. Dispatch the request to retrieve the response.
2017-07-05 02:21:46 +00:00
```rust
# use rocket::local::Client;
# let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap();
# let req = client.get("/");
let response = req.dispatch();
# let _ = response;
```
2017-07-05 02:21:46 +00:00
2018-10-16 06:24:23 +00:00
[`local`]: @api/rocket/local/
2018-10-16 05:50:35 +00:00
[`Client`]: @api/rocket/local/struct.Client.html
[`LocalRequest`]: @api/rocket/local/struct.LocalRequest.html
[`Rocket`]: @api/rocket/struct.Rocket.html
2017-07-05 02:21:46 +00:00
## 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>`.
2018-10-16 05:50:35 +00:00
[`LocalResponse`]: @api/rocket/local/struct.LocalResponse.html
[`Response`]: @api/rocket/struct.Response.html
[`status`]: @api/rocket/struct.Response.html#method.status
[`content_type`]: @api/rocket/struct.Response.html#method.content_type
[`headers`]: @api/rocket/struct.Response.html#method.headers
[`body_string`]: @api/rocket/struct.Response.html#method.body_string
[`body_bytes`]: @api/rocket/struct.Response.html#method.body_bytes
2017-07-05 02:21:46 +00:00
These methods are typically used in combination with the `assert_eq!` or
`assert!` macros as follows:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# use std::io::Cursor;
# use rocket::Response;
# use rocket::http::Header;
# #[get("/")]
# fn hello() -> Response<'static> {
# Response::build()
# .header(ContentType::Plain)
# .header(Header::new("X-Special", ""))
# .sized_body(Cursor::new("Expected Body"))
# .finalize()
# }
use rocket::local::Client;
use rocket::http::{ContentType, Status};
let rocket = rocket::ignite().mount("/", routes![hello]);
2017-07-05 02:21:46 +00:00
let client = Client::new(rocket).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
2017-07-05 02:21:46 +00:00
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()));
2017-07-05 02:21:46 +00:00
```
2017-07-05 02:21:46 +00:00
## Testing "Hello, world!"
2017-07-05 02:21:46 +00:00
To solidify an intuition for how Rocket applications are tested, we walk through
how to test the "Hello, world!" application below:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
#[get("/")]
fn hello() -> &'static str {
"Hello, world!"
}
fn rocket() -> rocket::Rocket {
2017-07-05 02:21:46 +00:00
rocket::ignite().mount("/", routes![hello])
}
2017-07-05 02:21:46 +00:00
fn main() {
# if false {
2017-07-05 02:21:46 +00:00
rocket().launch();
# }
2017-07-05 02:21:46 +00:00
}
```
2017-07-05 02:21:46 +00:00
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.
2017-07-05 02:21:46 +00:00
### Setting Up
First, we'll create a `test` module with the proper imports:
```rust
#[cfg(test)]
mod test {
use super::rocket;
use rocket::local::blocking::Client;
2017-07-05 02:21:46 +00:00
use rocket::http::Status;
#[test]
fn hello_world() {
/* .. */
}
}
```
2017-07-05 02:21:46 +00:00
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
2017-07-05 02:21:46 +00:00
#[cfg(test)] mod tests;
```
2017-07-05 02:21:46 +00:00
### Testing
To test our "Hello, world!" application, we create a `Client` for our
2017-07-05 02:21:46 +00:00
`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
# fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::Client;
2017-07-05 02:21:46 +00:00
let client = Client::new(rocket()).expect("valid rocket instance");
```
2017-07-05 02:21:46 +00:00
Then, we create a new `GET /` request and dispatch it, getting back our
application's response:
```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::Client;
# let client = Client::new(rocket()).expect("valid rocket instance");
2017-07-05 02:21:46 +00:00
let mut response = client.get("/").dispatch();
```
2017-07-05 02:21:46 +00:00
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!".
2017-07-05 02:21:46 +00:00
We do this by checking the `Response` object directly:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/")]
# fn hello() -> &'static str { "Hello, world!" }
# use rocket::local::Client;
use rocket::http::{ContentType, Status};
#
# let rocket = rocket::ignite().mount("/", routes![hello]);
# let client = Client::new(rocket).expect("valid rocket instance");
# let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Hello, world!".into()));
2017-07-05 02:21:46 +00:00
```
2017-07-05 02:21:46 +00:00
That's it! Altogether, this looks like:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
#[get("/")]
fn hello() -> &'static str {
"Hello, world!"
}
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![hello])
}
# /*
2017-07-05 02:21:46 +00:00
#[cfg(test)]
# */
2017-07-05 02:21:46 +00:00
mod test {
use super::rocket;
use rocket::local::blocking::Client;
2017-07-05 02:21:46 +00:00
use rocket::http::Status;
# /*
2017-07-05 02:21:46 +00:00
#[test]
# */ pub
2017-07-05 02:21:46 +00:00
fn hello_world() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
2017-07-05 02:21:46 +00:00
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Hello, world!".into()));
2017-07-05 02:21:46 +00:00
}
}
# fn main() { test::hello_world(); }
```
2017-07-05 02:21:46 +00:00
The tests can be run with `cargo test`. You can find the full source code to
2018-10-16 05:50:35 +00:00
[this example on GitHub](@example/testing).
## Asynchronous Testing
You may have noticed the use of a "`blocking`" API in these examples, even
though `Rocket` is an `async` web framework. In most situations, the `blocking`
testing API is easier to use. When concurrent execution of two or more requests
is required for the server to make progress, you will need the more flexible
`asynchronous` API; the `blocking` API is not capable of dispatching multiple
requests simultaneously. For more information, see the [`rocket::local`] and
[`rocket::local::asynchronous`] documentation as well as the asynchronous
version of [the `testing` example].
[`rocket::local`]: @api/rocket/local/index.html
[`rocket::local::asynchronous`]: @api/rocket/local/asynchronous/index.html
[the `testing` example]: @example/testing/src/main.rs
## Codegen Debug
2017-07-05 02:21:46 +00:00
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:
```sh
ROCKET_CODEGEN_DEBUG=1 cargo build
```
2017-07-05 02:21:46 +00:00
During compilation, you should see output like:
```rust,ignore
note: emitting Rocket code generation debug output
--> examples/hello_world/src/main.rs:7:1
|
7 | #[get("/")]
| ^^^^^^^^^^^
|
= note:
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)
}
```
2017-07-05 02:21:46 +00:00
This corresponds to the facade request handler Rocket has generated for the
`hello` route.