Rocket/site/guide/8-testing.md

296 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
2020-07-11 16:41:53 +00:00
# use rocket::local::blocking::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
2020-07-11 16:41:53 +00:00
# use rocket::local::blocking::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
2020-07-11 16:41:53 +00:00
# use rocket::local::blocking::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
# #[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", ""))
2020-07-11 16:41:53 +00:00
# .sized_body("Expected Body".len(), Cursor::new("Expected Body"))
# .finalize()
# }
2020-07-11 16:41:53 +00:00
use rocket::local::blocking::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());
2020-07-11 16:41:53 +00:00
assert_eq!(response.into_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
# #[macro_use] extern crate rocket;
#[get("/")]
fn hello() -> &'static str {
"Hello, world!"
}
#[rocket::launch]
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
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() }
2020-07-11 16:41:53 +00:00
# use rocket::local::blocking::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() }
2020-07-11 16:41:53 +00:00
# use rocket::local::blocking::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
# #[macro_use] extern crate rocket;
# #[get("/")]
# fn hello() -> &'static str { "Hello, world!" }
2020-07-11 16:41:53 +00:00
# use rocket::local::blocking::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
# #[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`
2020-07-09 01:36:28 +00:00
testing API is easier to use and should be preferred. However, 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. While synthetic, the
[`async_required` `testing` example] uses an `async` barrier to demonstrate such
a case. For more information, see the [`rocket::local`] and
[`rocket::local::asynchronous`] documentation.
[`rocket::local`]: @api/rocket/local/index.html
[`rocket::local::asynchronous`]: @api/rocket/local/asynchronous/index.html
2020-07-09 01:36:28 +00:00
[`async_required` `testing` example]: @example/testing/src/async_required.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.