seesharper/LightInject

Issue w/ Lazy<> and Scoped unit tests.

danyhoron opened this issue · 2 comments

First I want to congratulate you on these awesome projects (LightInject and LigtInject.xUnit) they are awesome.
I'm having an issue when I try to unit test a service that has another service Lazy<> injected.
To demonstrate the issue I created this project: https://github.com/danyhoron/lightinject-scope-issue

I used LightInject.xUnit and LightInject.
When I try to run the unit test I get this error: Attempt to create a scoped instance without a current scope.

Can you please take a look?

Thank you.

Hi Daniel and thanks for those kind words 👍

I see that you use LightInject.xUnit.core which is a package containing a fork of the original source. :)

The thing is that LightInject.xUnit relies on AppDomains which is not really a thing anymore in .Net Core.

It seems like the scope is already ended when the test actually executes. This could happen if xUnit is resolving the arguments on a different logical thread context from the one that is actually executing the test. I can't say for sure what is happening here :)

Given the dependency on AppDomains and the inner workings in xUnit, I have in recent projects used a much simpler approach.
(I'll PR the solution to you sample repo).

The solution is simply to use a base class like this

public class ContainerFixture : IDisposable
    {
        public ContainerFixture()
        {
            var container = CreateContainer();
            container.RegisterFrom<CompositionRoot>();
            Configure(container);
            ServiceFactory = container.BeginScope();
            InjectPrivateFields();
        }

        private void InjectPrivateFields()
        {
            var privateInstanceFields = this.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (var privateInstanceField in privateInstanceFields)
            {
                privateInstanceField.SetValue(this, GetInstance(ServiceFactory, privateInstanceField));
            }
        }

        internal Scope ServiceFactory { get; }

        public void Dispose() => ServiceFactory.Dispose();

        public TService GetInstance<TService>(string name = "")
            => ServiceFactory.GetInstance<TService>(name);

        private object GetInstance(IServiceFactory factory, FieldInfo field)
            => ServiceFactory.TryGetInstance(field.FieldType) ?? ServiceFactory.GetInstance(field.FieldType, field.Name);

        internal virtual IServiceContainer CreateContainer() => new ServiceContainer();

        internal virtual void Configure(IServiceRegistry serviceRegistry) { }
    }


Note that I have moved the registration of services into a composition root.

With this in place this is how the test class would look like. (See PR for further details)

    public class UnitTest1 : ContainerFixture
    {
        public IService01 svc01;

        public IService02 svc02;


        [Fact]
        public void Test1()
        {
            var value = svc01.GenerateValue();
            var echo = svc02.Echo02(value);
        }
    }

This works beautifully because xUnit will create an instance of your test class (fixture) for each test method.

Also worth noting here is that the ContainerFixture registers the composition root and then calls the virtual Configure method that can be used to override/mock services within the container.

Hope this helps 👍

That's great.
Thank you so much for that and for your quick response.

Have a great day.