Unit testing async functions that take references

Hello! I've got some code I'm testing which looks sort of like this:

#[cfg(test)]
mod tests {
    use tokio::io::AsyncWriteExt;

    async fn my_function<F: tokio::io::AsyncWrite + Unpin>(
        foo: &mut F,
    ) -> tokio::io::Result<usize> {
        Ok(foo.write(b"Hello!").await?)
    }

    #[tokio::test]
    async fn my_test() {
        let mut f = tokio::io::stdout();
        let handle = tokio::spawn(my_function(&mut f));

        handle.await.expect("my function");
    }
}

playground link

When I run this, I get this:

error[E0597]: `f` does not live long enough
  --> src/lib.rs:14:47
   |
14 |         let handle = tokio::spawn(my_function(&mut f));
   |                                   ------------^^^^^^-
   |                                   |           |
   |                                   |           borrowed value does not live long enough
   |                                   argument requires that `f` is borrowed for `'static`
...
17 |     }
   |     - `f` dropped here while still borrowed

error: aborting due to previous error

which I sort of understand (although I would have hoped that waiting on the handle would have fixed this). My question is, how can you test code like this, which is async, and takes references?

Thanks so much for any help you can provide!

-Jack

There is something that is for exactly the same thing except with threads in crossbeam::scope.

There is a crate that seeks to do this for futures, but it hasn't been updated in a while:

I'm not sure if there are any other newer answers to that need.

Edit: Here's a newer one, but it doesn't have a crates.io release yet:

Thanks, @zicklag!! I wasn't aware of these. Unfortunately, all of them seem to use really old versions of either tokio or futures. :frowning:

It helps, though, to know that it's not just Another Dopey Error on my part. I'll peruse the code in those crates and see if I can cobble together a way around this.

-Jack

Do you need to spawn this future? Or can you do:


    #[tokio::test]
    async fn my_test() {
        let mut f = tokio::io::stdout();
        my_function(&mut f).await.expect("my function");
    }

Alas, I have to spawn it - there are actually two futures in my real example which communicate with each other.

However, I found something which seems to do the trick: https://docs.rs/tokio-test/0.2.0/tokio_test/task/index.html

The documentation is...well, nonexistent, but using that to spawn the task seems to remove the scoping issue.

Thanks, though!!!

-Jack

Gak, never mind. That seems to be some sort of mocking of spawn tasks, not something you can use to spawn in tests. :man_facepalming:.

sigh Any other ideas would be greatly appreciated. I'll post if I come across something that solves this.

You can move the problematic value into the spawned task.

#[tokio::test]
async fn my_test() {
    let mut f = tokio::io::stdout();
    let handle = tokio::spawn(async move {
        my_function(&mut f).await
    });

    handle.await.expect("my function");
}

playground

2 Likes

you can use mem::transmute to change lifetime of reference. i.e. let hachky: &'static T = unsafe { mem::transmute(&mut f) }

It is safe as long as you can guarantee that you properly await future to finish, before argument gets dropped

Don't do this. Just use an Arc if moving is not enough.

4 Likes

@jacklund :man_facepalming: I just realized, can't you just join! on the two futures?

join!(future1, future2);

That will make sure that they are awaited on before the end of the function and the references should still be valid then, I think...

I might be missing something.


Edit: It looks like it works:

( playground )

Gah, sorry, I lost track of things - I ended up just giving up for the moment, and got sidetracked on other things.

@alice I'll take a look and see if that will work for my issue. I think I tried that, but it's been a while, so I could be totally mistaken.

@DoumanAsh I agree with @alice on this one. I gave brief thought about using transmute, but it really feels like a Bad Idea.

@zicklag Hmmm, I didn't really think of it that way. I'll give that a try, too.

Again, apologies for the long wait for a response, and thanks everyone for the great suggestions. I'll let you all know how it goes.

-Jack

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.