Testing F# async workflows with xUnit.net's Task support

試著忘記壹切 提交于 2020-01-03 11:01:53

问题


I'm writing F# code and tests in xUnit 1.9.

For normal sync stuff, I simply return unit and all is well; but now, I'm migrating the sync innards to be async workflows.

In other words, my simple AAA is getting explicit Async usage pushed out into it as I refactor the system:

let [<Fact>] ``Can consume using NEventStore InMemory`` () = 
    let store = NesGateway.createInMemory ()

    let finalDirection = playCircuit store |> Async.RunSynchronously // <----- YUCK

    test <@ CounterClockWise = finalDirection @> 

To remedy this, I want to make the body of the test be async. However, to the best of my knowledge, xUnit.net only manages methods returning Task-derived types, and hence I need a safe way of having my async test body be wrapped suitably for xUnit.net's runner to pick it up and handle it appropriately.

What's the best way to express my above test ?


回答1:


As part of xUnit 2.2 Beta 3, @Brad Wilson has done the necessary to make it work natively directly. Hence one can now simply write:

let [<Fact>] ``Can consume NEventStore InMemory`` () = async {
    let store = NesGateway.createInMemory ()

    let! finalDirection = playCircuit store

    test <@ CounterClockWise = finalDirection @> }



回答2:


Unfortunately (to the best of my knowledge [which is not saying a lot, hence the question]), the cleanest that's possible is to use the Async.StartAsTask <| async applelation thusly:

let [<Fact>] ``Can consume NEventStore InMemory`` () = Async.StartAsTask <| async {
    let store = NesGateway.createInMemory ()

    let! finalDirection = playCircuit store

    test <@ CounterClockWise = finalDirection @> }

Or, for a little more safety (an async [Fact] will not be recognized as sync by xUnit.net if the return type is not [derived from] Task) and [arguably] cleanliness (reads better):

// Ensure we match the return type xUnit.net is looking for
let toFact computation : Task = Async.StartAsTask computation :> _

let [<Fact>] ``Can play a circuit using GES``() = toFact <| async { 
    use! store = createStore()
    let! finalDirection = playCircuit store
    CounterClockWise =! finalDirection }



回答3:


You can also create your own computation expression to encapsulate the Async.RunSynchronously call:

type AsyncTestBuilder() =
  member this.Bind (v: Async<'a>, c: 'a -> 'b) = v |> Async.RunSynchronously |> c;
  member this.Zero () = ()

let asyncTest = AsyncTestBuilder() 

let [<Fact>] ``Can consume using NEventStore InMemory`` () = asyncTest {
  let store = NesGateway.createInMemory ()

  let! finalDirection = playCircuit store  // <-- use let! instead of let

  test <@ CounterClockWise = finalDirection @> }



回答4:


Pre xUnit 2.2, you can use a TaskBuilder to yield a Task<T> using the same style as async :-

let [<Fact>] ``Can consume NEventStore InMemory`` () = task {
    let store = NesGateway.createInMemory ()

    let! finalDirection = playCircuit store

    test <@ CounterClockWise = finalDirection @> }

Excerpt (don't take it, take the full source as you'll find it relevant for all kinds of Task-munging):

open System.Threading
open System.Threading.Tasks

[<AbstractClass>]
type AsyncBuilderAbstract() =
    // AsyncBuilder is marked sealed, so we need this wrapper
    member __.Zero() = async.Zero()
    member __.Return t = async.Return t
    member __.ReturnFrom t = async.ReturnFrom t
    member __.Bind(f,g) = async.Bind(f,g)
    member __.Combine(f,g) = async.Combine(f,g)
    member __.Delay f = async.Delay f
    member __.While(c,b) = async.While(c,b)
    member __.For(xs,b) = async.For(xs,b)
    member __.TryWith(b,e) = async.TryWith(b,e)

type TaskBuilder(?ct : CancellationToken) =
    inherit AsyncBuilderAbstract()
    member __.Run f : Task<'T> = Async.StartAsTask(f, ?cancellationToken = ct)

type UntypedTaskBuilder(?ct : CancellationToken) =
    inherit AsyncBuilderAbstract()
    member __.Run f : Task = Async.StartAsTask(f, ?cancellationToken = ct) :> Task

let task = new TaskBuilder()


来源:https://stackoverflow.com/questions/25605130/testing-f-async-workflows-with-xunit-nets-task-support

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!