Unit Testing with Tokio

You've seen before how easy Rust makes it to include unit tests in your project. Tokio makes it just as easy to test asynchronous code.

As a reminder, here's a regular---synchronous---unit test:

fn main() { } #[cfg(test)] mod test { #[test] fn simple_test() { assert_eq!(2 + 2, 4); } }

Run the test with cargo test, and you prove that your computer can add 2 and 2:

running 1 test test test::simple_test ... ok

The Problem with Async Tests

The problem with using the regular #[test] syntax is that you can't use async functions from a synchronous context. It won't compile:

fn main() { } async fn double(n: i32) -> i32 { n * 2 } #[cfg(test)] mod test { use super::*; #[test] fn simple_test() { assert_eq!(2 + 2, 4); } #[test] fn will_not_work() { // This will not work because we are not in an async context let result = double(2); assert_eq!(result, 4); } }

Option 1: Build a context in each test

The long-form solution is to build an async context in each test. For example:

#![allow(unused)] fn main() { #[test] fn the_hard_way() { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap(); assert_eq!(rt.block_on(double(2)), 4); } }

When you start doing this in every test, you wind up with a huge set of unit tests---and a lot of boilerplate. Fortunately, Tokio provides a better way.

Option 2: Use tokio-test

Tokio provides an alternative test macro---like like the tokio::main macro---for your tests. It adds the boilerplate to build an async context for you:

#![allow(unused)] fn main() { #[tokio::test] async fn the_easy_way() { assert_eq!(double(2).await, 4); } }

The tokio::test macro creates a full multi-threaded runtime for you. It's a great way to get started with Tokio testing. You can use it as a full async context---awaiting, joining, spawning.

Option 3: Single-threaded Tokio Test

If you are executing in a single-threaded environment, you also want to test single-threaded. Testing single-threaded can also be a good way to catch those times you accidentally blocked.

To test single-threaded, use the tokio::test macro with the single_thread feature:

#![allow(unused)] fn main() { #[tokio::test(flavor = "current_thread")] async fn single_thread_tokio() { assert_eq!(double(2).await, 4); } }

Taming Multi-Threaded Tokio

Rust unit tests already run in a threaded context (multiple tests execute at once)---creating a thread pool encompassing every CPU your system has for a test is probably overkill. You can also decorate the tokio::test macro with the multi_thread feature to create a multi-threaded Tokio runtime with a limited number of threads:

#![allow(unused)] fn main() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn tamed_multi_thread() { assert_eq!(double(2).await, 4); } }