Search code examples
unit-testingtestingrustrust-tokio

Test does not compile: "the async keyword is missing from the function declaration"


I'm trying to get working tests in my project (src/subdir/subdir2/file.rs):

#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn test_format_str() {
        let src = "a";
        let expect = "a";
        assert_eq!(expect, src);
    }
}

And get this error compiling:

error: the async keyword is missing from the function declaration
   --> src\domain\models\product.rs:185:11
    |
185 |     async fn test_format_str() {
    |           ^^

error: aborting due to previous error

Which makes no sense to me since async is there.

My original plan was this:

#[cfg(test)]
mod tests {
    #[test]
    fn test_format_str() {
        let src = "a";
        let expect = "a";
        assert_eq!(expect, src);
    }
}

Since all tests aren't async, but that gives the same error:

error: the async keyword is missing from the function declaration
   --> src\domain\models\product.rs:185:5
    |
185 |     fn test_format_str() {
    |     ^^

error: aborting due to previous error

I'm using tokio = { version = "0.2.22", features = ["full"]}, exporting macros from src/main.rs.

I tried use test::test; to get the std test macro but that gives an ambiguous import compilation error.

I checked out this post Error in Rust unit test: "The async keyword is missing from the function declaration" but it doesn't address my issue as far as I can tell, I need the macro export.

Full reproducable example. Win10, rustc 1.46.0. Just a main.rs:

#[macro_use]
extern crate tokio;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    Ok(())
}

#[cfg(test)]
mod tests {
    #[test]
    async fn test_format_str() {
        let src = "a";
        let expect = "a";
        assert_eq!(expect, src);
    }
}

with a single dependency:

[dependencies]
tokio = { version = "0.2.22", features = ["full"]}

Removing

#[macro_use]
extern crate tokio;

and using tokio macros as tokio:: ex. tokio::try_join! solves the immediate problem, although it would be nice to know why this happens.


Solution

  • This is a bug in tokio_macros, versions 0.2.4 and 0.2.5. The following minimal example also fails to build:

    use tokio::test;
    
    #[test]
    async fn it_works() {}
    

    The underlying problem is with the code this test macro expands to. In the currently released version, it is roughly the following:

    #[test]
    fn it_works() {
        tokio::runtime::Builder::new()
            .basic_scheduler()
            .enable_all()
            .build()
            .unwrap()
            .block_on(async { {} })
    }
    

    Note the #[test] attribute. It is intended to refer to the standard test attribute, i.e. to the ordinary test function marker, but, since tokio::test is in scope, it is invoked instead again - and, since the new function is not async, it throws an error.

    The issue was fixed with this commit, where test is replaced with ::core::prelude::v1::test, i.e. explicitly pulled in from core. But the corresponding change didn't make it yet into the released version, and I suspect this won't be fast, since this is technically a breaking change - bumping minimally supported Rust version.
    For now, the only workaround seems not to use wildcard imports with tokio, either explicitly or through macro_use, and use anything you need explicitly instead.