Files
FlowScope/My project/Assets/FlowScope/Tests/EditMode/Container/ContainerTests.cs
2026-05-18 11:58:49 +08:00

218 lines
8.0 KiB
C#

using System;
using System.Collections.Generic;
using NUnit.Framework;
using FlowScope.Container;
namespace FlowScope.Tests.EditMode.Container
{
public sealed class ContainerTests
{
[Test]
public void RegisterInstance_Resolve_ReturnsSameInstanceAndDoesNotDisposeExternalInstance()
{
var container = new FlowScope.Container.Container();
var service = new DisposableService();
container.RegisterInstance<IService>(service);
Assert.AreSame(service, container.Resolve<IService>());
Assert.IsTrue(container.TryResolve<IService>(out var resolved));
Assert.AreSame(service, resolved);
container.Dispose();
Assert.IsFalse(service.IsDisposed);
}
[Test]
public void RegisterFactory_Resolve_CreatesOnceAndDisposesInReverseCreationOrder()
{
var disposeOrder = new List<string>();
var container = new FlowScope.Container.Container();
container.RegisterFactory<FirstDisposable>(_ => new FirstDisposable(disposeOrder));
container.RegisterFactory<SecondDisposable>(_ => new SecondDisposable(disposeOrder));
var first = container.Resolve<FirstDisposable>();
var second = container.Resolve<SecondDisposable>();
Assert.AreSame(first, container.Resolve<FirstDisposable>());
Assert.AreSame(second, container.Resolve<SecondDisposable>());
container.Dispose();
container.Dispose();
CollectionAssert.AreEqual(new[] { "second", "first" }, disposeOrder);
}
[Test]
public void CreateScope_Resolve_ReadsParentAndCanOverrideWithoutPollutingParent()
{
var parent = new FlowScope.Container.Container();
var parentService = new NamedService("parent");
var childService = new NamedService("child");
parent.RegisterInstance<IService>(parentService);
var child = parent.CreateScope();
Assert.AreSame(parentService, child.Resolve<IService>());
child.RegisterInstance<IService>(childService);
Assert.AreSame(childService, child.Resolve<IService>());
Assert.AreSame(parentService, parent.Resolve<IService>());
}
[Test]
public void RegisterInstance_WhenSameScopeAlreadyRegistered_Throws()
{
var container = new FlowScope.Container.Container();
container.RegisterInstance<IService>(new NamedService("first"));
var ex = Assert.Throws<InvalidOperationException>(
() => container.RegisterInstance<IService>(new NamedService("second")));
StringAssert.Contains("already registered", ex.Message);
}
[Test]
public void Dispose_ParentDisposesChildScopesBeforeOwnInstances()
{
var disposeOrder = new List<string>();
var parent = new FlowScope.Container.Container();
parent.RegisterFactory<FirstDisposable>(_ => new FirstDisposable(disposeOrder));
var child = parent.CreateScope();
child.RegisterFactory<SecondDisposable>(_ => new SecondDisposable(disposeOrder));
parent.Resolve<FirstDisposable>();
child.Resolve<SecondDisposable>();
parent.Dispose();
CollectionAssert.AreEqual(new[] { "second", "first" }, disposeOrder);
}
[Test]
public void RegisterType_Resolve_UsesGeneratedFactoryEntryPointForConstructorInjection()
{
var container = new FlowScope.Container.Container();
container.RegisterInstance<IService>(new NamedService("dependency"));
GeneratedFactories.Register<ConstructorInjectedService>(
c => new ConstructorInjectedService(c.Resolve<IService>()));
container.RegisterType<ConstructorInjectedService>();
var resolved = container.Resolve<ConstructorInjectedService>();
Assert.AreEqual("dependency", resolved.Service.Name);
}
[Test]
public void ReflectionFactoryBuilder_RegisterType_ResolvesConstructorDependencies()
{
var container = new FlowScope.Container.Container();
container.RegisterInstance<IService>(new NamedService("reflection"));
ReflectionFactoryBuilder.RegisterType<ConstructorInjectedService>(container);
var resolved = container.Resolve<ConstructorInjectedService>();
Assert.AreEqual("reflection", resolved.Service.Name);
}
[Test]
public void RegisterType_WhenFactoryEntryPointMissing_ThrowsClearError()
{
var container = new FlowScope.Container.Container();
var ex = Assert.Throws<InvalidOperationException>(() => container.RegisterType<UnregisteredGeneratedService>());
StringAssert.Contains("RegisterFactory", ex.Message);
}
[Test]
public void Resolve_WhenDependencyCycleExists_ThrowsWithCompleteDependencyChain()
{
var container = new FlowScope.Container.Container();
ReflectionFactoryBuilder.RegisterType<CycleA>(container);
ReflectionFactoryBuilder.RegisterType<CycleB>(container);
var ex = Assert.Throws<InvalidOperationException>(() => container.Resolve<CycleA>());
StringAssert.Contains(nameof(CycleA), ex.Message);
StringAssert.Contains(nameof(CycleB), ex.Message);
StringAssert.Contains($"{nameof(CycleA)} -> {nameof(CycleB)} -> {nameof(CycleA)}", ex.Message);
}
[Test]
public void DisposedContainer_RegisterAndResolveThrowObjectDisposedException()
{
var container = new FlowScope.Container.Container();
container.Dispose();
Assert.Throws<ObjectDisposedException>(() => container.RegisterInstance<IService>(new NamedService("late")));
Assert.Throws<ObjectDisposedException>(() => container.RegisterFactory<IService>(_ => new NamedService("late")));
Assert.Throws<ObjectDisposedException>(() => container.Resolve<IService>());
Assert.Throws<ObjectDisposedException>(() => container.CreateScope());
}
private interface IService
{
string Name { get; }
}
private sealed class NamedService : IService
{
public NamedService(string name) => Name = name;
public string Name { get; }
}
private sealed class DisposableService : IService, IDisposable
{
public string Name => "disposable";
public bool IsDisposed { get; private set; }
public void Dispose() => IsDisposed = true;
}
private sealed class FirstDisposable : IDisposable
{
private readonly List<string> _disposeOrder;
public FirstDisposable(List<string> disposeOrder) => _disposeOrder = disposeOrder;
public void Dispose() => _disposeOrder.Add("first");
}
private sealed class SecondDisposable : IDisposable
{
private readonly List<string> _disposeOrder;
public SecondDisposable(List<string> disposeOrder) => _disposeOrder = disposeOrder;
public void Dispose() => _disposeOrder.Add("second");
}
[Injectable]
private sealed class ConstructorInjectedService
{
public ConstructorInjectedService(IService service) => Service = service;
public IService Service { get; }
}
[Injectable]
private sealed class UnregisteredGeneratedService
{
}
[Injectable]
private sealed class CycleA
{
public CycleA(CycleB dependency) => Dependency = dependency;
public CycleB Dependency { get; }
}
[Injectable]
private sealed class CycleB
{
public CycleB(CycleA dependency) => Dependency = dependency;
public CycleA Dependency { get; }
}
}
}