From 7413e25eec38666f1bdd91bf1305b5d1183be898 Mon Sep 17 00:00:00 2001 From: Joni Savolainen Date: Thu, 21 Sep 2023 20:50:27 +0300 Subject: [PATCH] feat: add abstract DependencyConsumer base class for DI consumers --- .../DependencyConsumerTests.cs | 89 +++++++++++++++++++ .../DependencyConsumerTests.cs.meta | 3 + .../DependencyAttribute.cs | 1 + .../DependencyInjection/DependencyConsumer.cs | 67 ++++++++++++++ .../DependencyConsumer.cs.meta | 3 + Assets/UIComponents/Core/UIComponent.cs | 1 + 6 files changed, 164 insertions(+) create mode 100644 Assets/UIComponents.Tests/DependencyConsumerTests.cs create mode 100644 Assets/UIComponents.Tests/DependencyConsumerTests.cs.meta create mode 100644 Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs create mode 100644 Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs.meta diff --git a/Assets/UIComponents.Tests/DependencyConsumerTests.cs b/Assets/UIComponents.Tests/DependencyConsumerTests.cs new file mode 100644 index 00000000..e3e84fa0 --- /dev/null +++ b/Assets/UIComponents.Tests/DependencyConsumerTests.cs @@ -0,0 +1,89 @@ +using NUnit.Framework; +using UIComponents.DependencyInjection; +using UIComponents.Testing; + +namespace UIComponents.Tests +{ + [TestFixture] + public partial class DependencyConsumerTests + { + private interface IStringDependency + { + string GetValue(); + } + + private interface IIntDependency + { + int GetValue(); + } + + public class StringProvider : IStringDependency + { + public string GetValue() => "Hello world"; + } + + public class EmptyStringProvider : IStringDependency + { + public string GetValue() => ""; + } + + [Dependency(typeof(IStringDependency), provide: typeof(StringProvider))] + private partial class StringDependencyConsumer : DependencyConsumer + { + [Provide] + public IStringDependency StringDependency; + + public bool TryProvideStringDependency(out IStringDependency dependency) + { + return TryProvide(out dependency); + } + + public bool TryProvideIntDependency(out IIntDependency dependency) + { + return TryProvide(out dependency); + } + } + + [Dependency(typeof(IStringDependency), provide: typeof(EmptyStringProvider))] + private partial class EmptyStringDependencyConsumer : StringDependencyConsumer {} + + [Test] + public void The_Correct_Class_Is_Provided_To_StringDependencyConsumer() + { + var component = new StringDependencyConsumer(); + Assert.That(component.StringDependency, Is.InstanceOf()); + } + + [Test] + public void The_Correct_Class_Is_Provided_To_EmptyStringDependencyConsumer() + { + var component = new EmptyStringDependencyConsumer(); + Assert.That(component.StringDependency, Is.InstanceOf()); + } + + [Test] + public void TryProvide_Works_For_EmptyStringDependencyConsumer() + { + var component = new EmptyStringDependencyConsumer(); + var couldProvideStringDependency = component.TryProvideStringDependency(out var stringDependency); + var couldProvideIntDependency = component.TryProvideIntDependency(out var intDependency); + + Assert.That(couldProvideStringDependency, Is.True); + Assert.That(stringDependency, Is.InstanceOf()); + Assert.That(couldProvideIntDependency, Is.False); + Assert.That(intDependency, Is.Null); + } + + [Test] + public void TestBed_Works_With_DependencyConsumers() + { + var stringProvider = new StringProvider(); + + var component = new TestBed() + .WithSingleton(stringProvider) + .Instantiate(); + + Assert.That(component.StringDependency, Is.SameAs(stringProvider)); + } + } +} \ No newline at end of file diff --git a/Assets/UIComponents.Tests/DependencyConsumerTests.cs.meta b/Assets/UIComponents.Tests/DependencyConsumerTests.cs.meta new file mode 100644 index 00000000..1bb56b04 --- /dev/null +++ b/Assets/UIComponents.Tests/DependencyConsumerTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1b2ead16350643ff8b3cc2d4a8ba401c +timeCreated: 1695317338 \ No newline at end of file diff --git a/Assets/UIComponents/Core/DependencyInjection/DependencyAttribute.cs b/Assets/UIComponents/Core/DependencyInjection/DependencyAttribute.cs index 55a3582c..efde4757 100644 --- a/Assets/UIComponents/Core/DependencyInjection/DependencyAttribute.cs +++ b/Assets/UIComponents/Core/DependencyInjection/DependencyAttribute.cs @@ -10,6 +10,7 @@ namespace UIComponents /// /// /// + /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Assembly, AllowMultiple = true, Inherited = true)] [ExcludeFromCoverage] [BaseTypeRequired(typeof(IDependencyConsumer))] diff --git a/Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs b/Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs new file mode 100644 index 00000000..e99cede5 --- /dev/null +++ b/Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; + +namespace UIComponents.DependencyInjection +{ + /// + /// An abstract base class for classes which want to access + /// UIComponents's dependency injection system. + /// + /// + /// [Dependency(typeof(IMyDependency), provide: typeof(MyDependency))] + /// public class MyClass : DependencyConsumer + /// { + /// [Provide] + /// private IMyDependency _myDependency; + /// } + /// + /// + /// + /// + /// + public abstract class DependencyConsumer : IDependencyConsumer + { + private static readonly IDependency[] EmptyDependencies = Array.Empty(); + + private readonly DependencyInjector _dependencyInjector; + + internal DependencyConsumer() + { + DiContext.Current.RegisterConsumer(this); + _dependencyInjector = DiContext.Current.GetInjector(GetType()); + + // ReSharper disable once VirtualMemberCallInConstructor + UIC_PopulateProvideFields(); + } + + /// + /// Returns a dependency. Throws a + /// if the dependency can not be provided. + /// + /// Dependency type + /// + /// Thrown if the dependency can not be provided + /// + /// Dependency instance + protected T Provide() where T : class + { + return _dependencyInjector.Provide(); + } + + /// + /// Attempts to provide a dependency. Returns whether + /// the dependency could be provided. + /// + /// Dependency instance + /// Dependency type + /// Whether the dependency could be fetched + protected bool TryProvide(out T instance) where T : class + { + return _dependencyInjector.TryProvide(out instance); + } + + public virtual IEnumerable GetDependencies() => EmptyDependencies; + + protected virtual void UIC_PopulateProvideFields() {} + } +} \ No newline at end of file diff --git a/Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs.meta b/Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs.meta new file mode 100644 index 00000000..5dc2755a --- /dev/null +++ b/Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cf339915dc37467db4c9e9eff3f092e0 +timeCreated: 1695316641 \ No newline at end of file diff --git a/Assets/UIComponents/Core/UIComponent.cs b/Assets/UIComponents/Core/UIComponent.cs index 2cecc297..f38c7088 100644 --- a/Assets/UIComponents/Core/UIComponent.cs +++ b/Assets/UIComponents/Core/UIComponent.cs @@ -68,6 +68,7 @@ protected UIComponent() _dependencyInjector = DiContext.Current.GetInjector(GetType()); AssetResolver = Provide(); Logger = Provide(); + // ReSharper disable once VirtualMemberCallInConstructor UIC_PopulateProvideFields(); DependencySetupProfilerMarker.End();