Closed shack05 closed 2 years ago
@gasparnagy Any ideas what is happening here?
Are BeforeTestRun
hooks supposed to run once for each thread? It seems that the registrations defined in the hook are only being run once and therefore the registrations are only being applied to the first test thread container?
Here's another example showing a similar exception. In this example I am registering an instance rather than a concrete type for an interface.
Example code:
public class Person
{
public Person(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
public string Name { get; }
}
[Binding]
public sealed class RegistrationHooks
{
[BeforeTestRun]
public static void RegisterTestThreadDependencies(TestThreadContext testThreadContext)
{
var person = new Person("Foo");
testThreadContext.TestThreadContainer.RegisterInstanceAs(person);
}
}
[Binding]
public sealed class CalculatorStepDefinitions
{
private readonly Person _person;
public CalculatorStepDefinitions(Person person)
{
_person = person;
}
...
}
Error:
Message:
Test method SpecFlowProject2.Features.CalculatorFeature.AddTwoNumbers threw exception:
BoDi.ObjectContainerException: Primitive types or structs cannot be resolved: System.String (resolution path: SpecFlowProject2.Steps.CalculatorStepDefinitions->SpecFlowProject2.Steps.Person)
Stack Trace:
ObjectContainer.ResolveObject(RegistrationKey keyToResolve, ResolutionList resolutionPath)
ObjectContainer.Resolve(Type typeToResolve, ResolutionList resolutionPath, String name)
<>c__DisplayClass71_0.<ResolveArguments>b__0(ParameterInfo p)
SelectArrayIterator`2.ToArray()
Enumerable.ToArray[TSource](IEnumerable`1 source)
ObjectContainer.ResolveArguments(IEnumerable`1 parameters, RegistrationKey keyToResolve, ResolutionList resolutionPath)
ObjectContainer.CreateObject(Type type, ResolutionList resolutionPath, RegistrationKey keyToResolve)
<>c__DisplayClass3_0.<ResolvePerContext>b__1()
RegistrationWithStrategy.ExecuteWithLock(Object lockObject, Func`1 getter, Func`1 factory, ResolutionList resolutionPath)
TypeRegistration.ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath)
RegistrationWithStrategy.Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath)
ObjectContainer.ResolveObject(RegistrationKey keyToResolve, ResolutionList resolutionPath)
ObjectContainer.Resolve(Type typeToResolve, ResolutionList resolutionPath, String name)
<>c__DisplayClass71_0.<ResolveArguments>b__0(ParameterInfo p)
SelectArrayIterator`2.ToArray()
Enumerable.ToArray[TSource](IEnumerable`1 source)
ObjectContainer.ResolveArguments(IEnumerable`1 parameters, RegistrationKey keyToResolve, ResolutionList resolutionPath)
ObjectContainer.CreateObject(Type type, ResolutionList resolutionPath, RegistrationKey keyToResolve)
<>c__DisplayClass3_0.<ResolvePerContext>b__1()
RegistrationWithStrategy.ExecuteWithLock(Object lockObject, Func`1 getter, Func`1 factory, ResolutionList resolutionPath)
TypeRegistration.ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath)
RegistrationWithStrategy.Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath)
ObjectContainer.ResolveObject(RegistrationKey keyToResolve, ResolutionList resolutionPath)
ObjectContainer.Resolve(Type typeToResolve, ResolutionList resolutionPath, String name)
ObjectContainer.Resolve(Type typeToResolve, String name)
TestObjectResolver.ResolveBindingInstance(Type bindingType, IObjectContainer container)
lambda_method(Closure , IContextManager , Int32 )
BindingInvoker.InvokeBinding(IBinding binding, IContextManager contextManager, Object[] arguments, ITestTracer testTracer, TimeSpan& duration)
TestExecutionEngine.ExecuteStepMatch(BindingMatch match, Object[] arguments, TimeSpan& duration)
TestExecutionEngine.ExecuteStep(IContextManager contextManager, StepInstance stepInstance)
TestExecutionEngine.OnAfterLastStep()
TestRunner.CollectScenarioErrors()
CalculatorFeature.ScenarioCleanup()
CalculatorFeature.AddTwoNumbers() line 13
@shack05 The BeforeTestRun
is executed only once, so it will access only the first TestThreadContainer (the documentation seems to be wrong BTW).
In order to save something to the TestThreadContainer, like you want, I think what you could do is to make a BeforeScenario hook with a very small order number (Order = -1), get the TestThreadContainer, check if the type was registered with container.IsRegistered and register to it if not.
An alternative approach is to wrap the TestThreadContainer with a per-scenario object, like what you can see in the SpecFlow MasterClass sample app here
I close this issue now, but if my suggestions don't work, please feel free to reopen it.
🤦♂️no idea how I could have overlooked that. Thanks @gasparnagy!
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
SpecFlow Version
3.9.22
Which test runner are you using?
MSTest
Test Runner Version Number
2.2.7
.NET Implementation
.NET Core 3.1
Project Format of the SpecFlow project
Sdk-style project format
.feature.cs files are generated using
SpecFlow.Tools.MsBuild.Generation NuGet package
Test Execution Method
Visual Studio Test Explorer
SpecFlow Section in app.config or content of specflow.json
N/A
Issue Description
If I try to run features in parallel the following exception is thrown for the second scenario if a binding class has a dependency on an interface whose implementation is registered in the TestThreadContainer:
BoDi.ObjectContainerException: Interface cannot be resolved: ...
Full error:
Steps to Reproduce
Please see repro.
Link to Repro Project
repro. The registration hook, interface, and implementation are all in CalculatorStepDefinitions.