Testing F # async Workflows with xUnit.net Support

I am writing F # code and tests in xUnit 1.9 .

For normal synchronization work, I just return unit , and all is well; but now I am moving the synchronization internals to async workflows.

In other words, my simple AAA gets the explicit use of async pushed into it when I reorganize the system:

 let [<Fact>] ``Can consume using NEventStore InMemory`` () = let store = NesGateway.createInMemory () let finalDirection = playCircuit store |> Async.RunSynchronously // <----- YUCK test <@ CounterClockWise = finalDirection @> 

To fix this, I want the test body to be async . However, to my knowledge, xUnit.net only controls methods that return types of Task specific types, and therefore I need a safe way for my async test body to be wrapped comfortably for the xUnit.net runner to pick it up and handle it accordingly way.

What is the best way to express my test above?

+6
source share
4 answers

As part of xUnit 2.2 Beta 3, @Brad Wilson did the necessary to make it work directly . So now you can simply write:

 let [<Fact>] ``Can consume NEventStore InMemory`` () = async { let store = NesGateway.createInMemory () let! finalDirection = playCircuit store test <@ CounterClockWise = finalDirection @> } 
+8
source

Unfortunately (as far as I know (which does not say much, therefore, the question)), the cleanest is to use the Async.StartAsTask <| async Async.StartAsTask <| async this way:

 let [<Fact>] ``Can consume NEventStore InMemory`` () = Async.StartAsTask <| async { let store = NesGateway.createInMemory () let! finalDirection = playCircuit store test <@ CounterClockWise = finalDirection @> } 

Or, for a bit more security (async [Fact] will not be recognized as xUnit.net sync if the return type is not [derived from] Task ) and [possibly] clean (read 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 } 
+5
source

You can also create your own calculation 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 @> } 
+2
source

Pre-xUnit 2.2, you can use TaskBuilder to get 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 @> } 

Extract (do not accept it, take the full source , as you will find it relevant for all types 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() 
0
source

Source: https://habr.com/ru/post/974642/


All Articles