218 lines
8.0 KiB
C#
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; }
|
|
}
|
|
}
|
|
}
|