Note: this was written when Async had just recently landed in stable rust and the library ecosystem for it was still settling down.
TLDR: Use tokio’s #[tokio::test]
macro to write quick and easy tests in Rust using async
features.
Backstory
I looked all over the internet and couldn’t find a nice way to test those pieces of code that are async
functions in Rust.
BTW if you haven’t read What Color is Your Function?, do that now.
Attempts
So here’s what I did.
1. futures-await-test
I looked up “async tests rust” and found this crate: https://github.com/ngg/futures-await-test
I copied their test.rs
file and it worked fine. However, when I tried running it on my code, it failed with a 'not currently running on the Tokio runtime.'
error.
#[cfg(test)]
mod tests {
use crate::plugin::*;
use super::*;
use crate::plugin::Requestor;
// use futures;
use futures_await_test::async_test;
#[async_test]
async fn get() -> AResult<()> {
let mut r = super::Requestor{};
r.configure(Config{version: "".to_string()});
// futures::executor::block_on
let res = r.make_request("https://google.com").await;
println!("{:#?}", res?);
Ok(())
}
}
I installed cargo-expand
and looked at the code that is generated from the macro. I couldn’t see anything egregious, so I tried something else.
2. futures::executor::block_on
I went lower level, and wrote a regular #[test]
fn
, but used futures
and block_on
. Unfortunately, I got the exact same error.
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::Requestor;
use crate::plugin::*;
use futures;
#[test]
fn get() -> AResult<()> {
let mut r = super::Requestor {};
r.configure(Config {
version: "".to_string(),
})?;
futures::executor::block_on(async {
let res = r.make_request("https://google.com").await;
match res {
Ok(r) => println!("{:#?}", r),
Err(e) => panic!(e),
}
});
Ok(())
}
}
3. tokio
Maybe this process illustrates my relative foolishness with Rust so far, and my finally understanding the need for an async
runtime. I guess I just thought futures
did that automatically.
I am curious if I could use a tokio
runtime but a regular fn
like above.
The sad thing is that the #[tokio::test]
macro is so poorly documented and hidden that it was the last thing I tried.
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::Requestor;
use crate::plugin::*;
#[tokio::test]
async fn get() -> AResult<()> {
let mut r = super::Requestor{};
r.configure(Config {
version: "".to_string(),
})?;
let url = "https://google.com";
println!("calling {}", url);
let res = r.make_request(url).await?;
println!("{:#?}", res);
Ok(())
}
}
Anyways, lesson learned, and a few Rust debugging skills were luckily picked up on the way:
cargo test -- --nocapture
- prints output from testscargo test 2>/dev/null
- throws warning and errors awaycargo expand
- view generated code from macros- Also, stacktraces from inside anything async are a pain!