简体   繁体   中英

How can I unit test an Asynchronous method in F#?

I've got a method that returns a task I'm trying to test synchonously in F#. Here's the method, which is an implementation of a C# interface:

member this.RunAsync(): System.Threading.Tasks.Task =             
    async{
        this._settings.LogSettings |> Seq.iter(fun settings -> this.clearLogs(settings)) |> ignore
        return ()
        } |> Async.StartAsTask :> _

Here's my test logic

Async.AwaitTask(logFileManager.RunAsync())|> Async.Ignore
// and I've tried
async {
  logFileManager.RunAsync() 
  Assert.Pass()
} |> Async.RunSynchronously

The test runs, and with 5 items, the expected number of calls should be 5, but the test fails with a random number of calls between 1 and 2.

The same test in C# for another implementation is simply:

[Test]
public async Task Should_do_something_in_an_asynchronous_method()
{
    var unitUnderTest = GetService();
    await unitUnderTest.RunAsync();
}

How do I ensure the task is completed for the unit tests?

How do I write the equivalent test method with async ?

Take a look at the await word in your C# code. What do you think it does? What if you dropped it? Would the test still work?

The answers are: "It makes the async call part of the surrounding async computation" and "No, it wouldn't"

It wouldn't, because the call to RunAsync would no longer be a part of the surrounding computation, and the surrounding computation wouldn't know that it had to "wait" for it to complete.

And this is exactly what you're doing in your F# code: you're just calling the function and forgetting about it, not making it part of the surrounding workflow. The function goes off and starts working on its own, and your async workflow goes off in a different direction.

In F#, the way to make nested calls a part of the surrounding flow is with let! or do! keywords, which are somewhat analogous to await in C#. Like this:

let f = async { ... }
let g = async {
    let! x = f
    printfn "f returned: %A" x
}

But in your case this wouldn't work right away, because RunAsync returns a C# Task , not an F# Async . So you need to also convert from the former to the latter. To do the conversion, use the Async.AwaitTask function:

async {
  do! logFileManager.RunAsync() |> Async.AwaitTask
  Assert.Pass()
} |> Async.RunSynchronously

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM