Skip to content

Latest commit

 

History

History
111 lines (81 loc) · 2.86 KB

TokioTesting.md

File metadata and controls

111 lines (81 loc) · 2.86 KB

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:

#[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:

#[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:

#[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:

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