Skip to content

Commit

Permalink
feat: add abstract DependencyConsumer base class for DI consumers (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonisavo committed Sep 21, 2023
1 parent b6e219c commit 424143b
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 0 deletions.
98 changes: 98 additions & 0 deletions Assets/UIComponents.Tests/DependencyConsumerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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 consumer = new StringDependencyConsumer();
Assert.That(consumer.StringDependency, Is.InstanceOf<StringProvider>());
}

[Test]
public void The_Correct_Class_Is_Provided_To_EmptyStringDependencyConsumer()
{
var consumer = new EmptyStringDependencyConsumer();
Assert.That(consumer.StringDependency, Is.InstanceOf<EmptyStringProvider>());
}

private class EmptyDependencyConsumer : DependencyConsumer {}

[Test]
public void DependencyConsumers_Have_No_Dependencies_By_Default()
{
var consumer = new EmptyDependencyConsumer();
Assert.That(consumer.GetDependencies(), Is.Empty);
}

[Test]
public void TryProvide_Works_For_EmptyStringDependencyConsumer()
{
var consumer = new EmptyStringDependencyConsumer();
var couldProvideStringDependency = consumer.TryProvideStringDependency(out var stringDependency);
var couldProvideIntDependency = consumer.TryProvideIntDependency(out var intDependency);

Assert.That(couldProvideStringDependency, Is.True);
Assert.That(stringDependency, Is.InstanceOf<EmptyStringProvider>());
Assert.That(couldProvideIntDependency, Is.False);
Assert.That(intDependency, Is.Null);
}

[Test]
public void TestBed_Works_With_DependencyConsumers()
{
var stringProvider = new StringProvider();

var consumer = new TestBed<StringDependencyConsumer>()
.WithSingleton<IStringDependency>(stringProvider)
.Instantiate();

Assert.That(consumer.StringDependency, Is.SameAs(stringProvider));
}
}
}
3 changes: 3 additions & 0 deletions Assets/UIComponents.Tests/DependencyConsumerTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace UIComponents
/// </summary>
/// <seealso cref="UIComponent"/>
/// <seealso cref="IDependencyConsumer"/>
/// <seealso cref="DependencyConsumer"/>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Assembly, AllowMultiple = true, Inherited = true)]
[ExcludeFromCoverage]
[BaseTypeRequired(typeof(IDependencyConsumer))]
Expand Down
67 changes: 67 additions & 0 deletions Assets/UIComponents/Core/DependencyInjection/DependencyConsumer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;

namespace UIComponents.DependencyInjection
{
/// <summary>
/// An abstract base class for classes which want to access
/// UIComponents's dependency injection system.
/// <example>
/// <code>
/// [Dependency(typeof(IMyDependency), provide: typeof(MyDependency))]
/// public class MyClass : DependencyConsumer
/// {
/// [Provide]
/// private IMyDependency _myDependency;
/// }
/// </code>
/// </example>
/// <seealso cref="DependencyAttribute"/>
/// <seealso cref="ProvideAttribute"/>
/// </summary>
public abstract class DependencyConsumer : IDependencyConsumer
{
private static readonly IDependency[] EmptyDependencies = Array.Empty<IDependency>();

private readonly DependencyInjector _dependencyInjector;

internal DependencyConsumer()
{
DiContext.Current.RegisterConsumer(this);
_dependencyInjector = DiContext.Current.GetInjector(GetType());

// ReSharper disable once VirtualMemberCallInConstructor
UIC_PopulateProvideFields();
}

/// <summary>
/// Returns a dependency. Throws a <see cref="MissingProviderException"/>
/// if the dependency can not be provided.
/// </summary>
/// <typeparam name="T">Dependency type</typeparam>
/// <exception cref="MissingProviderException">
/// Thrown if the dependency can not be provided
/// </exception>
/// <returns>Dependency instance</returns>
protected T Provide<T>() where T : class
{
return _dependencyInjector.Provide<T>();
}

/// <summary>
/// Attempts to provide a dependency. Returns whether
/// the dependency could be provided.
/// </summary>
/// <param name="instance">Dependency instance</param>
/// <typeparam name="T">Dependency type</typeparam>
/// <returns>Whether the dependency could be fetched</returns>
protected bool TryProvide<T>(out T instance) where T : class
{
return _dependencyInjector.TryProvide(out instance);
}

public virtual IEnumerable<IDependency> GetDependencies() => EmptyDependencies;

protected virtual void UIC_PopulateProvideFields() {}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Assets/UIComponents/Core/UIComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ protected UIComponent()
_dependencyInjector = DiContext.Current.GetInjector(GetType());
AssetResolver = Provide<IAssetResolver>();
Logger = Provide<ILogger>();
// ReSharper disable once VirtualMemberCallInConstructor
UIC_PopulateProvideFields();

DependencySetupProfilerMarker.End();
Expand Down

0 comments on commit 424143b

Please sign in to comment.