1

How do you test your futures which are meant to be run in the Tokio runtime?

fn fut_dns() -> impl Future<Item = (), Error = ()> {
    let f = dns::lookup("www.google.de", "127.0.0.1:53");
    f.then(|result| match result {
        Ok(smtptls) => {
            println!("{:?}", smtptls);
            assert_eq!(smtptls.version, "TLSRPTv1");
            assert!(smtptls.rua.len() > 0);
            assert_eq!(smtptls.rua[0], "mailto://...");
            ok(())
        }
        Err(e) => {
            println!("error: {:?}", e);
            err(())
        }
    })
}

#[test]
fn smtp_log_test() {
    tokio::run(fut_dns());
    assert!(true);
}

The future runs and the thread of the future panics on an assert. You can read the panic in the console, but the test doesn't recognize the threads of tokio::run.

The How can I test a future that is bound to a tokio TcpStream? doesn't answer this, because it simply says: A simple way to test async code may be to use a dedicated runtime for each test

I do this!

My question is related to how the test can detect if the future works. The future needs a started runtime environment.

The test is successful although the future asserts or calls err().

So what can I do?

Markus
  • 512
  • 1
  • 4
  • 21

1 Answers1

2

Do not write your assertions inside the future.

As described in How can I test a future that is bound to a tokio TcpStream?, create a Runtime to execute your future. As described in How do I synchronously return a value calculated in an asynchronous Future in stable Rust?, compute your value and then exit the async world:

fn run_one<F>(f: F) -> Result<F::Item, F::Error>
where
    F: IntoFuture,
    F::Future: Send + 'static,
    F::Item: Send + 'static,
    F::Error: Send + 'static,
{
    let mut runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime");
    runtime.block_on(f.into_future())
}

#[test]
fn smtp_log_test() {
    let smtptls = run_one(dns::lookup("www.google.de", "127.0.0.1:53")).unwrap();
    assert_eq!(smtptls.version, "TLSRPTv1");
    assert!(smtptls.rua.len() > 0);
    assert_eq!(smtptls.rua[0], "mailto://...");
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Yes, that is what I am looking for. The tokio people should update their docs. They write their: most people should use tokio::run(). That's misleading. – Markus Oct 30 '18 at 17:18
  • @Markus it's not misleading, it's just not addressing people trying to write tests. Most people **do** want to use `tokio::run` in their production code. – Shepmaster Oct 30 '18 at 17:40
  • This solution seems more complicated than it needs to be (unless I'm confused, which is likely enough). I think the body of `run_one` can be the single expression: `f.into_future().wait()`. Taking that thought a step further, `run_one` could go away by making the first line of the test: `let smtptls = dns::lookup("www.google.de", "127.0.0.1:53").wait().unwrap();` – Bruce Adams Apr 01 '19 at 16:31
  • 1
    @BruceAdams check out [How do I synchronously return a value calculated in an asynchronous Future in stable Rust?](https://stackoverflow.com/q/52521201/155423). `wait` has limitations (*not appropriate to call on event loops or similar I/O situations*) so without further context about where `run_one` will be called, it's safer to to spin up a Tokio `Runtime`. If `wait` works for your case, then you can use it. – Shepmaster Apr 01 '19 at 16:35