How to write Rust unit tests for your Compute application
How do we know our edge applications work? I’m supremely confident when writing code, but sometimes my inflated expectations do not match reality. That’s why I write tests for my code.
Fastly’s Compute allows you to build high-scale, globally distributed applications and execute code at the edge. Today, we’ll write some unit tests for a Rust application using Viceroy, which provides local testing for developers working with Compute.
Running an application
Let’s get started by installing the Fastly CLI and installing Rust. In a new directory, set up a new Rust basic starter kit that demonstrates routing and simple synthetic responses:
fastly compute init
Pick Rust as the language, and the Default starter for Rust kit.
This generates a working application in the directory. The core logic takes place in src/main.rs.
To build and then run this application locally, run the following:
fastly compute serve
...
INFO: Listening on http://127.0.0.1:7676
When you visit http://127.0.0.1:7676, you should see a friendly page confirming that everything worked.
Testing the application
The code appears to work, but now we need to write tests to verify its correctness.
Let’s install Viceroy, which will let us run the tests locally, and install cargo-nextest, which will run the tests for us in their own Wasm instance and then aggregate the results.
Add the following to your project's .cargo/config:
[target.wasm32-wasi]
runner = "viceroy run -C fastly.toml -- "
We have to rewrite the main function in src/main.rs to make it testable. Change it to:
fn main() -> Result<(), Error> {
let ds_req = Request::from_client();
let us_resp = handler(ds_req)?;
us_resp.send_to_client();
Ok(())
}
fn handler(req: Request) -> Result<Response, Error> {
...
Now we can write some simple tests that use the fastly crate.
There are three main areas we can test:
the logic which rejects unexpected methods
the logic which returns the synthetic response
the logic which returns a 404 status code
For each of these areas, we will make a request and check the response status code, content type and body. Add these functions to the end of the file:
#[test]
fn test_post() {
let req = fastly::Request::post("http://example.com/");
let resp = handler(req).expect("request succeeds");
assert_eq!(resp.get_status(), StatusCode::METHOD_NOT_ALLOWED);
assert_eq!(resp.get_content_type(), Some(mime::TEXT_PLAIN_UTF_8));
assert_eq!(resp.into_body_str(), "This method is not allowed\n");
}
#[test]
fn test_homepage() {
let req = fastly::Request::get("http://example.com/");
let resp = handler(req).expect("request succeeds");
assert_eq!(resp.get_status(), StatusCode::OK);
assert_eq!(resp.get_content_type(), Some(mime::TEXT_HTML_UTF_8));
assert!(resp.into_body_str().contains("Welcome to Compute@Edge"));
}
#[test]
fn test_missing() {
let req = fastly::Request::get("http://example.com/missing");
let resp = handler(req).expect("request succeeds");
assert_eq!(resp.get_status(), StatusCode::OK);
assert_eq!(resp.get_content_type(), Some(mime::TEXT_PLAIN_UTF_8));
assert_eq!(
resp.into_body_str(),
"The page you requested could not be found\n"
);
}
We can run tests with cargo nextest run. If a sneaky bug had crept into the handler, it might result in an error like this, indicating that it expected a 404 status code but instead received a 200 status code:
cargo nextest run
...
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `404`,
right: `200`', src/main.rs:122:5
Canceling due to test failure: 0 tests still running
------------
Summary [ 3.125s] 5 tests run: 4 passed, 1 failed, 0 skipped
FAIL [ 3.122s] fastly-compute-project::bin/fastly-compute-project test_missing
error: test run failed
However, today our handler is gloriously bug-free and works as expected. Running the tests results in a positive summary:
cargo nextest run
...
Starting 3 tests across 1 binary
PASS [ 1.596s] fastly-compute-project::bin/fastly-compute-project test_post
PASS [ 1.885s] fastly-compute-project::bin/fastly-compute-project test_homepage
PASS [ 1.900s] fastly-compute-project::bin/fastly-compute-project test_missing
------------
Summary [ 1.902s] 3 tests run: 3 passed, 0 skipped
Testing the Waters
Now you know the details of how to unit test Rust Compute applications. How could we take this further? I recommend splitting complicated logic into small, testable functions. And if you’re up for a challenge, how about writing the tests before writing the function itself? Test away! If you’re not yet using our serverless platform, explore Compute in depth — for free.