reqnroll / Reqnroll

Open-source Cucumber-style BDD test automation framework for .NET.
https://reqnroll.net
BSD 3-Clause "New" or "Revised" License
310 stars 32 forks source link

MissingMethodException when using Reqnroll.BoDi with a Plugin #91

Closed mbhoek closed 3 months ago

mbhoek commented 3 months ago

Reqnroll Version

1.0.2-local (vNext)

Which test runner are you using?

xUnit

Test Runner Version Number

2.5.6

.NET Implementation

.NET 6.0

Test Execution Method

Visual Studio Test Explorer

Content of reqnroll.json configuration file

No response

Issue Description

Using a DI container plugin results in a System.MissingMethodException : Method not found: 'Reqnroll.BoDi.ObjectContainer Reqnroll.Plugins.ObjectContainerEventArgs.get_ObjectContainer()' (full exception below) when using Reqnroll vNext (which includes Reqnroll.BoDi as part of the solution). The exception does not occur using Reqnroll v1.0.1.

I first encountered this exception whilst porting the Microsoft.Extensions.DependencyInjection integration plugin to Reqnroll. Thinking I made a mistake during the port, I created a simple test project using the Autofac integration plugin (see link to repro project). This resulted in the exact same exception.

Full exception:

[xUnit.net 00:00:00.45]     Test a service [FAIL]
[xUnit.net 00:00:00.46]     [Test Class Cleanup Failure (Reqnroll.PluginTester.Features.TestFeature)] System.NullReferenceException
  Failed Test a service [1 ms]
  Error Message:
   System.AggregateException : One or more errors occurred. (Method not found: 'Reqnroll.BoDi.ObjectContainer Reqnroll.Plugins.ObjectContainerEventArgs.get_ObjectContainer()'.) (Method not found: 'Reqnroll.BoDi.ObjectContainer Reqnroll.Plugins.ObjectContainerEventArgs.get_ObjectContainer()'.)
---- System.MissingMethodException : Method not found: 'Reqnroll.BoDi.ObjectContainer Reqnroll.Plugins.ObjectContainerEventArgs.get_ObjectContainer()'.
---- System.MissingMethodException : Method not found: 'Reqnroll.BoDi.ObjectContainer Reqnroll.Plugins.ObjectContainerEventArgs.get_ObjectContainer()'.
  Stack Trace:

----- Inner Stack Trace #1 (System.MissingMethodException) -----
   at Reqnroll.Autofac.AutofacPlugin.<>c.<Initialize>b__1_0(Object sender, CustomizeGlobalDependenciesEventArgs args)
   at Reqnroll.Plugins.RuntimePluginEvents.RaiseCustomizeGlobalDependencies(ObjectContainer container, ReqnrollConfiguration reqnrollConfiguration)
   at Reqnroll.Infrastructure.ContainerBuilder.CreateGlobalContainer(Assembly testAssembly, IRuntimeConfigurationProvider configurationProvider)
   at Reqnroll.TestRunnerManager.CreateTestRunnerManager(Assembly testAssembly, IContainerBuilder containerBuilder)
   at Reqnroll.TestRunnerManager.<>c__DisplayClass38_0.<GetTestRunnerManager>b__0(Assembly assembly)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Reqnroll.TestRunnerManager.GetTestRunnerManager(Assembly testAssembly, IContainerBuilder containerBuilder, Boolean createIfMissing)
   at Reqnroll.TestRunnerManager.OnTestRunStartAsync(Assembly testAssembly, String testWorkerId, IContainerBuilder containerBuilder)
   at Reqnroll_PluginTester_XUnitAssemblyFixture.InitializeAsync() in C:\Users\Mark\Projects\Reqnroll.PluginTester\obj\Debug\net6.0\xUnit.AssemblyHooks.cs:line 18
   at Reqnroll.xUnit.ReqnrollPlugin.XunitTestAssemblyRunnerWithAssemblyFixture.<AfterTestAssemblyStartingAsync>b__2_0()
----- Inner Stack Trace #2 (System.MissingMethodException) -----
   at Reqnroll.Autofac.AutofacPlugin.<>c.<Initialize>b__1_0(Object sender, CustomizeGlobalDependenciesEventArgs args)
   at Reqnroll.Plugins.RuntimePluginEvents.RaiseCustomizeGlobalDependencies(ObjectContainer container, ReqnrollConfiguration reqnrollConfiguration)
   at Reqnroll.Infrastructure.ContainerBuilder.CreateGlobalContainer(Assembly testAssembly, IRuntimeConfigurationProvider configurationProvider)
   at Reqnroll.TestRunnerManager.CreateTestRunnerManager(Assembly testAssembly, IContainerBuilder containerBuilder)
   at Reqnroll.TestRunnerManager.<>c__DisplayClass38_0.<GetTestRunnerManager>b__0(Assembly assembly)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Reqnroll.TestRunnerManager.GetTestRunnerManager(Assembly testAssembly, IContainerBuilder containerBuilder, Boolean createIfMissing)
   at Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(Assembly testAssembly, String testWorkerId, IContainerBuilder containerBuilder)
   at Reqnroll.PluginTester.Features.TestFeature.FeatureSetupAsync()
   at Reqnroll.PluginTester.Features.TestFeature.FixtureData.Xunit.IAsyncLifetime.InitializeAsync()
  Failed Test a service [1 ms]
  Error Message:
   [Test Class Cleanup Failure (Reqnroll.PluginTester.Features.TestFeature)]: System.NullReferenceException : Object reference not set to an instance of an object.
  Stack Trace:
     at Reqnroll.PluginTester.Features.TestFeature.FeatureTearDownAsync()
   at Reqnroll.PluginTester.Features.TestFeature.FixtureData.Xunit.IAsyncLifetime.DisposeAsync()

Steps to Reproduce

1) Build Reqnroll vNext locally, this will result in v1.0.2-local NuGet packages for Reqnroll and related projects 2) Build the Repro Project with the v1.0.2-local NuGet packages (I use a local NuGet folder)

Steps to verify that it does work in v1.0.1: 1) Change the package versions in Reqnroll.PluginTester.csproj to v1.0.1

Link to Repro Project

https://github.com/mbhoek/Reqnroll.PluginTester

clrudolphi commented 3 months ago

I'm not seeing the same problem using your sample project. Here is a screenshot showing the test as successful along with the Package dependencies showing 1.0.2-local. Screenshot 2024-04-07 125223

Not that the above helps much, but thought you'd want to see that it works elsewhere. I'm not seeing anything obvious. Perhaps there is still something wonky with your dependencies?

gasparnagy commented 3 months ago

@mbhoek Try to delete the C:\Users\<youruser>\.nuget\packages\reqnroll* folders to make sure that you are really using the latest compiled Reqnroll local package.

mbhoek commented 3 months ago

@clrudolphi @gasparnagy Thanks for verifying. After deleting the .nuget folder and restarting Visual Studio multiple times it did start to work. Guess I need to be more diligent whilst working on this plugin.

mbhoek commented 3 months ago

@gasparnagy Reopening this to discuss a related Reqnroll.BoDi issue: it still outputs Reqnroll.Bodi as a separate NuGet package. This is package is also referenced as a dependency in Reqnroll.nuspec. Is that something we want to keep this way, or do we want BoDi to just be part of the Reqnroll package?

gasparnagy commented 3 months ago

@mbhoek I don't know, to be honest. There are pros and cons on both sides. My thinking was, that for the majority of the usages, it does not matter. You only need to add the dependency to the Reqnroll package and the BoDi package will be loaded implicitly, but if anyone used BoDi without SpecFlow (???) they could still migrate to Reqnroll.BoDi... Do you see any inconvenience or disadvantage of having it as a separate package?

ajeckmans commented 3 months ago

As long as someone is not loading a binary incompatible version of the bodi package alongside a reqnroll package with bodi compiled in it. It would fail at runtime when calling the incompatible code without any warning at compile time.

mbhoek commented 3 months ago

After a little postmortem I think that not having the correct Reqnroll.BoDi package in my local NuGet folder caused the original problem of this issue. That's why I was wondering why we still need it as a separate package; in my mind we don't need it anymore (I don't think BoDi is used a lot outside of SpecFlow/Reqnroll, but I might be wrong).

Taking this one step further: if we have integration plugins for all the major DI frameworks, do we even need BoDi at all? I can imagine a future where Reqnroll users not only select which testing framework they use (NUnit/xUnit/etc) but als which DI framework (instead of BoDi). Major change ofcourse, but it might make integration of DI frameworks easier in the end.

gasparnagy commented 3 months ago

@ajeckmans As long as someone is not loading a binary incompatible version of the bodi package alongside a reqnroll package with bodi compiled in it. It would fail at runtime when calling the incompatible code without any warning at compile time.

Maybe because it's too late, but I'm not sure I get your point. Is this an argument for the separate package or against it?

@mbhoek If we have integration plugins for all the major DI frameworks, do we even need BoDi at all?

The DI frameworks are too different and it would be very hard to keep a consistent behavior of Reqnroll itself if we would need to rely on the specific behavior of 5+ different DI frameworks. The current DI plugins provide an implementation of the ITestObjectResolver interface that is used to resolve only the objects of the user (step def classes, etc.), but even in this situation the Reqnroll internal infrastructure is created using BoDi. So we will need that. (Why we did not pick one selected DI framework and managed the SpecFlow classes with that is an interesting question: if we would have picked a version of a particular DI it would have caused conflict if the users would wanted to use a different version of the same DI.)

And if we anyway have BoDi, we can offer that as a default. It is good enough for the most of the needs anyway.

ajeckmans commented 3 months ago

Neither :) it is just that if you fully integrate into the source of Reqroll (so not referencing the package) and also still package the nuget, then people can run into issues. So the only safe options are the ones mentioned. Separate package and referencing in Reqroll or fully integrating into reqnroll and dropping the package.

Personally I think if someone really wants to use it outside of the reqnroll context they can use the specflow one or copy/paste the code in their solution. For anyone that is extending Reqroll (or using it in a reqnroll project), packaging it with Reqroll and keeping the interfaces public should suffice.

gasparnagy commented 3 months ago

@ajeckmans Got it now. :)

I feel like convinced about including it only to the Reqnroll package and not releasing it as a separate package. I will try to make tomorrow. (Note to self: this was the commit that did it.)

mbhoek commented 3 months ago

@gasparnagy And if we anyway have BoDi, we can offer that as a default. It is good enough for the most of the needs anyway.

I'm fine with BoDi, but as a plugin developer I found it cumbersome to 'hide' the DI framework inside BoDi with a level of indirection, so that's why I suggested it. Obviously we can continue with how things are right now. 👍