diff --git a/Examples/ByFeature/AsyncSteps/AsyncSteps.fsproj b/Examples/ByFeature/AsyncSteps/AsyncSteps.fsproj
new file mode 100644
index 0000000..0cb62f2
--- /dev/null
+++ b/Examples/ByFeature/AsyncSteps/AsyncSteps.fsproj
@@ -0,0 +1,22 @@
+
+
+
+
+ netcoreapp2.1;net452
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/ByFeature/AsyncSteps/NunitWiring.fs b/Examples/ByFeature/AsyncSteps/NunitWiring.fs
new file mode 100644
index 0000000..c9a74f4
--- /dev/null
+++ b/Examples/ByFeature/AsyncSteps/NunitWiring.fs
@@ -0,0 +1,46 @@
+module NUnit.TickSpec
+
+open TickSpec
+open NUnit.Framework
+
+open System.Reflection
+open System.Runtime.ExceptionServices
+
+/// Class containing all BDD tests in current assembly as NUnit unit tests
+[]
+type FeatureFixture () =
+ /// Test method for all BDD tests in current assembly as NUnit unit tests
+ []
+ []
+ member __.Bdd (scenario:Scenario) =
+ if scenario.Tags |> Seq.exists ((=) "ignore") then
+ raise (new IgnoreException("Ignored: " + scenario.ToString()))
+ try
+ scenario.Action.Invoke()
+ with
+ | :? TargetInvocationException as ex -> ExceptionDispatchInfo.Capture(ex.InnerException).Throw()
+
+ /// All test scenarios from feature files in current assembly
+ static member Scenarios =
+ let createFeatureData (feature:Feature) =
+ let createTestCaseData (feature:Feature) (scenario:Scenario) =
+ let enhanceScenarioName parameters scenarioName =
+ let replaceParameterInScenarioName (scenarioName:string) parameter =
+ scenarioName.Replace("<" + fst parameter + ">", snd parameter)
+ parameters
+ |> Seq.fold replaceParameterInScenarioName scenarioName
+ (new TestCaseData(scenario))
+ .SetName(enhanceScenarioName scenario.Parameters scenario.Name)
+ .SetProperty("Feature", feature.Name)
+ |> Seq.foldBack (fun (tag:string) data -> data.SetProperty("Tag", tag)) scenario.Tags
+ feature.Scenarios
+ |> Seq.map (createTestCaseData feature)
+
+ let assembly = Assembly.GetExecutingAssembly()
+ let definitions = new StepDefinitions(assembly.GetTypes())
+
+ assembly.GetManifestResourceNames()
+ |> Seq.filter (fun (n:string) -> n.EndsWith(".feature") )
+ |> Seq.collect (fun n ->
+ definitions.GenerateFeature(n, assembly.GetManifestResourceStream(n))
+ |> createFeatureData)
\ No newline at end of file
diff --git a/Examples/ByFeature/AsyncSteps/Time.feature b/Examples/ByFeature/AsyncSteps/Time.feature
new file mode 100644
index 0000000..13c2048
--- /dev/null
+++ b/Examples/ByFeature/AsyncSteps/Time.feature
@@ -0,0 +1,7 @@
+Feature: Time
+
+Scenario: Time
+ Given having current time
+ When I sleep for 20ms using Async
+ And I sleep for 20ms using Tasks
+ Then the current time is at least 40ms higher than it was
diff --git a/Examples/ByFeature/AsyncSteps/TimeSteps.fs b/Examples/ByFeature/AsyncSteps/TimeSteps.fs
new file mode 100644
index 0000000..fbfa888
--- /dev/null
+++ b/Examples/ByFeature/AsyncSteps/TimeSteps.fs
@@ -0,0 +1,27 @@
+module TimeSteps
+
+open NUnit.Framework
+open TickSpec
+open System
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open System.Threading.Tasks
+
+type Time =
+ | Time of DateTime
+
+let [] ``having current time`` () =
+ Time DateTime.Now
+
+let [] ``I sleep for (\d*)ms using Async`` (duration: int) =
+ async {
+ do! Async.Sleep duration
+ }
+
+let [] ``I sleep for (\d*)ms using Tasks`` (duration: int) =
+ task {
+ do! Task.Delay duration
+ }
+
+let [] ``the current time is at least (\d*)ms higher than it was`` (duration: int) (Time previousCurrentTime) =
+ int (DateTime.Now - previousCurrentTime).TotalMilliseconds >= duration
+ |> Assert.True
\ No newline at end of file
diff --git a/Examples/ByFeature/AsyncSteps/Web.feature b/Examples/ByFeature/AsyncSteps/Web.feature
new file mode 100644
index 0000000..ff24ff3
--- /dev/null
+++ b/Examples/ByFeature/AsyncSteps/Web.feature
@@ -0,0 +1,9 @@
+Feature: Web requests
+
+Scenario: Google contains Google
+ When I download https://www.google.com/ web page using Async
+ Then the downloaded page contains "Google"
+
+Scenario: Bing contains Bing
+ When I download https://www.bing.com/ web page using Tasks
+ Then the downloaded page contains "Bing"
\ No newline at end of file
diff --git a/Examples/ByFeature/AsyncSteps/WebSteps.fs b/Examples/ByFeature/AsyncSteps/WebSteps.fs
new file mode 100644
index 0000000..6e2a687
--- /dev/null
+++ b/Examples/ByFeature/AsyncSteps/WebSteps.fs
@@ -0,0 +1,33 @@
+module WebSteps
+
+open NUnit.Framework
+open TickSpec
+open System.Net
+open System
+open FSharp.Control.Tasks.V2.ContextInsensitive
+
+type DownloadedPage =
+ | DownloadedPage of string
+
+let [] ``I download (.*) web page using Async`` address =
+ async {
+ let req = WebRequest.Create(Uri address)
+ use! resp = req.AsyncGetResponse()
+ use stream = resp.GetResponseStream()
+ use reader = new IO.StreamReader(stream)
+ return reader.ReadToEnd() |> DownloadedPage
+ }
+
+let [] ``I download (.*) web page using Tasks`` address =
+ task {
+ let req = WebRequest.Create(Uri address)
+ use! resp = req.GetResponseAsync()
+ use stream = resp.GetResponseStream()
+ use reader = new IO.StreamReader(stream)
+ return reader.ReadToEnd() |> DownloadedPage
+ }
+
+let [] ``the downloaded page contains "(.*)"`` (text: string) (DownloadedPage page) =
+ page.Contains(text)
+ |> Assert.True
+
diff --git a/TickSpec.sln b/TickSpec.sln
index 9472a7c..949158f 100644
--- a/TickSpec.sln
+++ b/TickSpec.sln
@@ -58,6 +58,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TickSpec.Tests", "TickSpec.
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "TaggedExamples", "Examples\ByFeature\TaggedExamples\TaggedExamples.fsproj", "{39C63F8E-F3A5-48D8-851C-62BEB9C701C2}"
EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "AsyncSteps", "Examples\ByFeature\AsyncSteps\AsyncSteps.fsproj", "{B1CD5BF8-4A92-4005-909F-BCBFDE35D150}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -124,6 +126,10 @@ Global
{39C63F8E-F3A5-48D8-851C-62BEB9C701C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39C63F8E-F3A5-48D8-851C-62BEB9C701C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39C63F8E-F3A5-48D8-851C-62BEB9C701C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1CD5BF8-4A92-4005-909F-BCBFDE35D150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1CD5BF8-4A92-4005-909F-BCBFDE35D150}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1CD5BF8-4A92-4005-909F-BCBFDE35D150}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1CD5BF8-4A92-4005-909F-BCBFDE35D150}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -148,6 +154,7 @@ Global
{6DD7F109-6798-4CAF-BD13-7BA4689BD892} = {EB1F6262-0913-4464-A034-4F4D01268E83}
{1CE6B475-C94F-438E-97D4-5E99FD0F04D4} = {EB1F6262-0913-4464-A034-4F4D01268E83}
{39C63F8E-F3A5-48D8-851C-62BEB9C701C2} = {EB1F6262-0913-4464-A034-4F4D01268E83}
+ {B1CD5BF8-4A92-4005-909F-BCBFDE35D150} = {EB1F6262-0913-4464-A034-4F4D01268E83}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B381617-F1EE-4BFC-8EAE-CAB4DBB4B9A2}
diff --git a/TickSpec/AsyncInvoker.fs b/TickSpec/AsyncInvoker.fs
new file mode 100644
index 0000000..3c999f1
--- /dev/null
+++ b/TickSpec/AsyncInvoker.fs
@@ -0,0 +1,18 @@
+namespace TickSpec
+open System.Threading.Tasks
+
+type AsyncInvoker() =
+ static member DoTaskCall (task: Task) =
+ async {
+ do! task |> Async.AwaitTask
+ } |> Async.RunSynchronously
+
+ static member DoCallAsync<'T> (input: Task<'T>) =
+ async {
+ return! input |> Async.AwaitTask
+ } |> Async.RunSynchronously
+
+ static member DoAsyncCall<'T> (input: Async<'T>) =
+ async {
+ return! input
+ } |> Async.RunSynchronously
\ No newline at end of file
diff --git a/TickSpec/ScenarioGen.fs b/TickSpec/ScenarioGen.fs
index e4f7e09..133ec87 100644
--- a/TickSpec/ScenarioGen.fs
+++ b/TickSpec/ScenarioGen.fs
@@ -433,8 +433,28 @@ let defineStepMethod
else
gen.Emit(OpCodes.Callvirt, mi)
- if mi.ReturnType <> typeof then
- gen.Emit(OpCodes.Box,mi.ReturnType)
+ let v = mi.ReturnType
+ let typ =
+ match v.Namespace, v.Name with
+ | "System.Threading.Tasks", "Task`1" ->
+ let callInfo =
+ typeof.GetMethod("DoCallAsync", BindingFlags.Public ||| BindingFlags.Static).MakeGenericMethod(v.GenericTypeArguments.[0])
+ gen.EmitCall(OpCodes.Call, callInfo, null)
+ v.GenericTypeArguments.[0]
+ | "System.Threading.Tasks", "Task" ->
+ let callInfo =
+ typeof.GetMethod("DoTaskCall", BindingFlags.Public ||| BindingFlags.Static)
+ gen.EmitCall(OpCodes.Call, callInfo, null)
+ typeof
+ | "Microsoft.FSharp.Control", "FSharpAsync`1" ->
+ let callInfo =
+ typeof.GetMethod("DoAsyncCall", BindingFlags.Public ||| BindingFlags.Static).MakeGenericMethod(v.GenericTypeArguments.[0])
+ gen.EmitCall(OpCodes.Call, callInfo, null)
+ v.GenericTypeArguments.[0]
+ | _, _ -> v
+
+ if typ <> typeof then
+ gen.Emit(OpCodes.Box,typ)
let local0 = gen.DeclareLocal(typeof