hadashiA / VContainer

The extra fast, minimum code size, GC-free DI (Dependency Injection) library running on Unity Game Engine.
https://vcontainer.hadashikick.jp
MIT License
1.81k stars 157 forks source link

Registering and Resolving not executed when LifetimeScope parent not found #644

Open Maesla opened 4 months ago

Maesla commented 4 months ago

If a LifetimeScope defines a parent and the parent is not found, neither a exception is thrown nor registering and resolving is executed

LifetimeScope executes in its Awake GetRuntimeParent(), which does throw a VContainerParentTypeReferenceNotFound. However, this exception is silently handled by the Awake's try catch here and added to a waiting list.

The resulting behavior is that, when you run a scene with a LifetimeScope with a parent defined, and the parent is not found, you don't get any error or exception and you don't get neither any registering nor injection

Video

https://github.com/hadashiA/VContainer/assets/25863696/ee3d6c55-5e19-41e9-ba11-9ea1b6c7d86b

Test

using NUnit.Framework;
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using VContainer;
using VContainer.Tests.Unity;
using VContainer.Unity;

public class ParentNotFoundExceptionTest
{

    private class Foo
    {
        public int value;

        public Foo()
        {
            this.value = -1;
        }
    }

    private class TestLifeTimeScope : LifetimeScope
    {
        public bool buildCallbackCalled = false;
        public bool configureCalled = false;

        protected override void Awake(){} //empty to do nothing in AddComponent

        //like awake, but on demand
        public void Init()
        {

            // this method calls to base.Awake()
            // base.Awake() calls to Build
            // Build to GetRuntimeParent
            // GetRuntimeParent throws VContainerParentTypeReferenceNotFound
            // The exception is handled in the Awake again without rethrowing
            base.Awake(); 
        }

        protected override void Configure(IContainerBuilder builder)
        {
            configureCalled = true;

            builder.RegisterBuildCallback(container =>
            {
                buildCallbackCalled = true;
            });

            builder.Register<Foo>(Lifetime.Scoped);
        }
    }

    [UnityTest]
    public IEnumerator VContainerParentTypeReferenceNotFoundTest()
    {
        GameObject go = new GameObject("TestLifeTimeScope");
        TestLifeTimeScope testLifetimeScope = go.AddComponent<TestLifeTimeScope>();
        testLifetimeScope.autoRun = true;

        testLifetimeScope.parentReference = ParentReference.Create<SampleChildLifetimeScope2>();

        bool exceptionThrown = false;
        try
        {
            testLifetimeScope.Init();
        }
        catch (VContainerParentTypeReferenceNotFound) //parent doesn't exist, so the exception should be thrown
        {
            exceptionThrown = true;
        }

        ////Expected 1. Exception thrown and registering and injection not done
        //Assert.That(exceptionThrown, Is.True, "Exception not thrown");
        //Assert.That(testLifetimeScope.buildCallbackCalled, Is.False, "Build Callback not called");
        //Assert.That(testLifetimeScope.configureCalled, Is.False, "Configure called");
        //Assert.Throws<System.NullReferenceException>(() => testLifetimeScope.Container.Resolve<Foo>());

        ////Expected 2. Exception not thrown and registering and injection done
        //Assert.That(exceptionThrown, Is.False, "Exception not thrown");
        //Assert.That(testLifetimeScope.buildCallbackCalled, Is.True, "Build Callback not called");
        //Assert.That(testLifetimeScope.configureCalled, Is.True, "Configure called");
        //Assert.DoesNotThrow(() => testLifetimeScope.Container.Resolve<Foo>());

        //Current Behavior
        Assert.That(exceptionThrown, Is.False, "Exception not thrown");
        Assert.That(testLifetimeScope.buildCallbackCalled, Is.False, "Build Callback not called");
        Assert.That(testLifetimeScope.configureCalled, Is.False, "Configure called");
        Assert.Throws<System.NullReferenceException>(() => testLifetimeScope.Container.Resolve<Foo>());

        yield return null;
    }
}

Scene Test

ParentNotFound.zip