SpecFlowOSS / SpecFlow

#1 .NET BDD Framework. SpecFlow automates your testing & works with your existing code. Find Bugs before they happen. Behavior Driven Development helps developers, testers, and business representatives to get a better understanding of their collaboration
https://www.specflow.org/
Other
2.22k stars 752 forks source link

Type registered in BeforeTestRun method is not disposed #2614

Open qrjo opened 2 years ago

qrjo commented 2 years ago

SpecFlow Version

3.9.74

Which test runner are you using?

NUnit

Test Runner Version Number

3.13.3

.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

When registering a type in the IObjectContainer from a binding method with the BeforeTestRun attribute, the type is constructed at the start of the test run, but it is not disposed at the end of the test run. This is different from registering from e.g. a BeforeScenario method, where the type is constructed before every scenario and is then disposed after every scenario.

Steps to Reproduce

Use the following code and put breakpoints in the dispose methods. Make sure to either put "When test1" or "When test2" (or both) in a scenario.

using System;
using BoDi;
using TechTalk.SpecFlow;

namespace TestDispose
{
    [Binding]
    public class TestBinding
    {
        private readonly TestClass1 _test1;
        private readonly TestClass2 _test2;

        public TestBinding(TestClass1 test1, TestClass2 test2)
        {
            _test1 = test1;
            _test2 = test2;
        }

        [When(@"test1")]
        public void Test1()
        {
            _test1.Test();
        }

        [When(@"test2")]
        public void Test2()
        {
            _test2.Test();
        }
    }

    [Binding]
    public class TestHook
    {
        [BeforeTestRun]
        public static void BeforeTestRun(IObjectContainer container)
        {
            container.RegisterTypeAs<TestClass1, TestClass1>();
        }

        [BeforeScenario]
        public static void BeforeScenario(IObjectContainer container)
        {
            container.RegisterTypeAs<TestClass2, TestClass2>();
        }
    }

    public sealed class TestClass1 : IDisposable
    {
        public TestClass1()
        {
        }

        public void Test()
        {
        }

        public void Dispose()
        {
        }
    }

    public sealed class TestClass2 : IDisposable
    {
        public TestClass2()
        {
        }

        public void Test()
        {
        }

        public void Dispose()
        {
        }
    }
}

Link to Repro Project

No response

SabotageAndi commented 1 year ago

Did you repo this also without breakpoints? From my experience with the test runners, they don't give you much time to do stuff in a test run tear down, so it could simply be that they end the execution before the code is executed.

qrjo commented 1 year ago

Yeah, I've tried that too, but it's still not hitting the Dispose method of TestClass1.

As for not having enough time to do anything: AfterTestRun methods work as intended, so I don't think time should be an issue.

clrudolphi commented 1 year ago

I have investigated this a bit and have a few suggestions.

  1. The first option is not use the BeforeTestRun hook, but to use the BeforeFeature hook. As long as the lifecycle and concurrency requirements of your object(s) can live with being instantiated and disposed for each Feature, this might be a cleaner way.

  2. If use of BeforeTestRun is necessary, the following will work: ` [BeforeTestRun] public static void BeforeTestRun(ObjectContainer container) { container.BaseContainer.RegisterTypeAs<TestClass1, TestClass1>();

        var tc1 = container.Resolve<TestClass1>();
    }

    ` This approach injects the ObjectContainer class rather than the interface and relies on the knowledge that the TestThread container's BaseContainer is the global container. The global container is reliably disposed of (while the TestThread container does not appear to be). This approach is less clean than the above because it does rely on the current structure of SF classes which might change in the future.

  3. @SabotageAndi - The TestThreadContext is Disposable. But the Dispose method does not dispose of its Container. Is that a bug or by design? `public class TestThreadContext : SpecFlowContext, ITestThreadContext { public event Action Disposing; public IObjectContainer TestThreadContainer { get; }

    public TestThreadContext(IObjectContainer testThreadContainer)
    {
        TestThreadContainer = testThreadContainer;
    }
    
    protected override void Dispose()
    {
        Disposing?.Invoke(this);
        base.Dispose();
    }

    } ` HTH