fsprojects / TickSpec

Lean .NET BDD framework with powerful F# integration
Apache License 2.0
133 stars 23 forks source link

Functional injection with BeforeScenario attribute throws System.InvalidProgramException in .NET Framework project #35

Closed lkaczanowski closed 5 years ago

lkaczanowski commented 5 years ago

I'm working on .NET Framework test project with TickSpec and NUnit. When using BeforeScenario attribute with functional injection coding style I am getting System.InvalidProgramException.

Steps to reproduce on TickSpec solution:

  1. Change target of Examples.ByFeature.FunctionalInjection project to .NET Framework 4.7.2 only
  2. Change file StockSteps.fs to:
    
    module StockStepDefinitions

open NUnit.Framework open Retail open TickSpec

let [] BeforeScenario () = { Count = -1 }

let [] a customer buys a black jumper () = ()

let [] I have (.*) black jumpers left in stock (n:int) (stockItem:StockItem) =
{ stockItem with Count = n }

let [] he returns the jumper for a refund (stockItem:StockItem) =
{ stockItem with Count = stockItem.Count + 1 }

let [] I should have (.*) black jumpers in stock (n:int) (stockItem:StockItem) =
let passed = (stockItem.Count = n) Assert.True(passed)

3. Run tests for project

You should get test result like below:

[0-1001]NUnit.TickSpec+FeatureFixture.Scenario 1: Refunded items should be returned to stock

System.InvalidProgramException : Common Language Runtime detected an invalid program. at Scenario 1: Refunded items should be returned to stock.Run() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at NUnit.TickSpec.FeatureFixture.Bdd(Scenario scenario) in C:\sources\TickSpec\Examples\ByFeature\FunctionalInjection\NunitWiring.fs:line 21



The issue is definitely with code generation for .NET Framework runtime.
The interesting thing is when I reverted commit 7ed773afc4f369ebd35fd417085098cb6d1c175b (Enable .NET Framework features again) then everything worked properly even when test project was targeting .NET 4.7.2.
Maybe this code generation is needed only for .NET Framework 4.5 exclusively?
michalkovy commented 5 years ago

TickSpec has two implementations of execution:

  1. Simple execution: feature files are parsed and proper methods executed based on that
  2. IL Generation: feature files are parsed and based on them IL code for execution is generated. The generated IL is then used for execution. The benefit of this implementation is that you can add a debugging breakpoint to a feature file and see how it steps through when debugging tests.

.NET Core implementation doesn't support second type of implementation as we haven't find a way to tell in generated IL how it maps to a feature file. So, when you use today NUnit fixture it will typically use second implementation for .NET framework tests and first implementation for .NET Core tests.

I was focusing on step methods only when I was implementing functional injection. The reason that registration within BeforeScenario works in "simple execution" case is a luck, the implementation reuses invoke method between step methods and events (like BeforeScenario).

It makes sense to improve the implementation to support functional injection for events in IL generation case (ScenarioGen.fs). Note that I am not sure that "simple execution" handles requesting something from DI container within events - we may want to implement that, too. Let me know if you want to work on that, I can help.

Workaround for you can be to use "simple execution" implementation - e.g. by using .NET standard version of TickSpec or by modifying FeatureFixture.cs in your project so that it uses the ScenarioRun type of the execution.

lkaczanowski commented 5 years ago

Thank You for the quick answer. I will use workaround for now but I can help fixing this issue as well. I can start working on it next week though.