Skip to content

Commit

Permalink
feat: rework dependency injection to use source generation
Browse files Browse the repository at this point in the history
Dependency injection is reworked completely to use source generation.

BREAKING CHANGE: All UIComponent subclasses must now be declared partial. TestBed has been reworked to focus on testing one component at a time. `new TestBed<TComponent>` is used instantiate one now.
  • Loading branch information
jonisavo committed Nov 20, 2022
1 parent 3de820a commit fe70a79
Show file tree
Hide file tree
Showing 74 changed files with 1,575 additions and 1,191 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ crashlytics-build.properties
/artifacts/
*.coverage
/dist
coveragereport/
TestResults/
2 changes: 1 addition & 1 deletion Assets/Samples/Counter/CounterComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace UIComponents.Samples.Counter
{
[Dependency(typeof(ICounterService), provide: typeof(CounterService))]
public class CounterComponent : UIComponent
public partial class CounterComponent : UIComponent
{
private readonly ICounterService _counterService;

Expand Down
2 changes: 1 addition & 1 deletion Assets/UIComponents.Benchmarks/BenchmarkUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static void MeasureComponentInitWithColdCache<TComponent>() where TCompon
})
.SetUp(() =>
{
DiContext.Current.Container.Clear();
DiContext.Current.Clear();
})
.SampleGroup(new SampleGroup("Cold Cache Time"))
.ProfilerMarkers(GetProfilerMarkers())
Expand Down
12 changes: 6 additions & 6 deletions Assets/UIComponents.Benchmarks/UIComponentInitBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

namespace UIComponents.Benchmarks
{
public class UIComponentInitBenchmarks
public partial class UIComponentInitBenchmarks
{
private class EmptyComponent : UIComponent {}
private partial class EmptyComponent : UIComponent {}

private interface IDependency {}
private interface IMockDependency {}

private class DependencyProvider : IDependency {}
private class DependencyProvider : IMockDependency {}

[Dependency(typeof(IDependency), provide: typeof(DependencyProvider))]
private class ComponentWithDependency : UIComponent {}
[Dependency(typeof(IMockDependency), provide: typeof(DependencyProvider))]
private partial class ComponentWithDependency : UIComponent {}

[Test, Performance, Version(BenchmarkUtils.Version)]
public void InitializeEmptyComponentWithWarmCache()
Expand Down
4 changes: 2 additions & 2 deletions Assets/UIComponents.Tests/DebugLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
namespace UIComponents.Tests
{
[TestFixture]
public class DebugLoggerTests
public partial class DebugLoggerTests
{
private class TestComponent : UIComponent {}
private partial class TestComponent : UIComponent {}

private TestComponent _testComponent;

Expand Down
244 changes: 123 additions & 121 deletions Assets/UIComponents.Tests/DependencyInjectorTests.cs
Original file line number Diff line number Diff line change
@@ -1,88 +1,129 @@
using System;
using NUnit.Framework;
using NUnit.Framework;
using UIComponents.DependencyInjection;
using UIComponents.Tests.Utilities;

namespace UIComponents.Tests
{
[TestFixture]
public class DependencyInjectorTests
{
private interface IDependency {}
private interface IMockDependency {}

public class DependencyOne : IDependency {}
public class DependencyOne : IMockDependency {}

public class DependencyTwo : IDependency {}
public class DependencyTwo : IMockDependency {}

[Test]
public void Can_Be_Created_Using_DependencyAttributes()
private IDependencyConsumer _mockConsumer;
private DiContext _diContext;

[SetUp]
public void SetUp()
{
var dependencyAttributes = new[]
_mockConsumer = MockUtilities.CreateDependencyConsumer(new IDependency[]
{
new DependencyAttribute(typeof(IDependency), typeof(DependencyOne))
};
Dependency.SingletonFor<IMockDependency, DependencyOne>()
});

var injector = new DependencyInjector(dependencyAttributes, DiContext.Current.Container);
_diContext = new DiContext();
_diContext.RegisterConsumer(_mockConsumer);
}

[Test]
public void Can_Be_Created_Without_Consumer()
{
var injector = new DependencyInjector(_diContext);

Assert.That(injector.Provide<IDependency>(), Is.InstanceOf<DependencyOne>());
Assert.That(injector.HasConsumer, Is.False);
}

[Test]
public void Can_Be_Created_Using_Consumer()
{
var injector = new DependencyInjector(_mockConsumer, _diContext);

Assert.That(injector.HasConsumer, Is.True);
}

[TestFixture]
public class Provide
{
[Test]
public void Returns_Desired_Dependency()
private DiContext _diContext;
private IDependencyConsumer _mockConsumer;
private IDependencyConsumer _mockConsumerWithTransientOverride;

[SetUp]
public void SetUp()
{
var injector = new DependencyInjector(DiContext.Current.Container);
injector.SetDependency<IDependency>(new DependencyOne());
Assert.That(injector.Provide<IDependency>(), Is.InstanceOf<DependencyOne>());
_diContext = new DiContext();
_mockConsumer = MockUtilities.CreateDependencyConsumer(new IDependency[]
{
Dependency.SingletonFor<IMockDependency, DependencyOne>()
});
_mockConsumerWithTransientOverride = MockUtilities.CreateDependencyConsumer(new IDependency[]
{
Dependency.TransientFor<IMockDependency, DependencyOne>(),
Dependency.SingletonFor<IMockDependency, DependencyTwo>()
});
_diContext.RegisterConsumer(_mockConsumer);
_diContext.RegisterConsumer(_mockConsumerWithTransientOverride);
}

[Test]
public void Returns_Desired_Dependency_With_Non_Generic_Method()
public void Returns_Desired_Dependency()
{
var injector = new DependencyInjector(DiContext.Current.Container);
injector.SetDependency<IDependency>(new DependencyOne());
Assert.That(injector.Provide(typeof(IDependency)), Is.InstanceOf<DependencyOne>());
var injector = new DependencyInjector(_mockConsumer, _diContext);
Assert.That(injector.Provide<IMockDependency>(), Is.InstanceOf<DependencyOne>());
}

[Test]
public void Throws_If_No_Provider_Exists()
public void Returns_Transient_Dependency_If_Singleton_And_Transient_Are_Registered()
{
var injector = new DependencyInjector(DiContext.Current.Container);

var exception = Assert.Throws<MissingProviderException>(
() => injector.Provide<IDependency>()
);

Assert.That(exception.Message, Is.EqualTo("No provider found for IDependency"));
var injector = new DependencyInjector(_mockConsumerWithTransientOverride, _diContext);
Assert.That(injector.Provide<IMockDependency>(), Is.InstanceOf<DependencyOne>());
}

private interface IMissingDependency {}

[Test]
public void Throws_If_No_Provider_Exists_With_Non_Generic_Method()
public void Throws_If_No_Provider_Exists()
{
var injector = new DependencyInjector(DiContext.Current.Container);
var injector = new DependencyInjector(_mockConsumer, _diContext);

var exception = Assert.Throws<MissingProviderException>(
() => injector.Provide(typeof(IDependency))
() => injector.Provide<IMissingDependency>()
);

Assert.That(exception.Message, Is.EqualTo("No provider found for IDependency"));
Assert.That(exception.Message, Is.EqualTo("No provider found for IMissingDependency"));
}
}

[TestFixture]
public class TryProvide
{
private DiContext _diContext;
private IDependencyConsumer _mockConsumer;

[SetUp]
public void SetUp()
{
_diContext = new DiContext();
_mockConsumer = MockUtilities.CreateDependencyConsumer(new IDependency[]
{
Dependency.SingletonFor<IMockDependency, DependencyOne>()
});
_diContext.RegisterConsumer(_mockConsumer);
}

private interface IMissingDependency {}

[Test]
public void Returns_If_Dependency_Could_Be_Provided()
{
var injector = new DependencyInjector(DiContext.Current.Container);

Assert.That(injector.TryProvide<IDependency>(out _), Is.False);
var injector = new DependencyInjector(_mockConsumer, _diContext);

injector.SetDependency<IDependency>(new DependencyOne());
Assert.That(injector.TryProvide<IMissingDependency>(out _), Is.False);

var wasProvided = injector.TryProvide<IDependency>(out var instance);
var wasProvided = injector.TryProvide<IMockDependency>(out var instance);

Assert.That(wasProvided, Is.True);
Assert.That(instance, Is.InstanceOf<DependencyOne>());
Expand All @@ -91,116 +132,77 @@ public void Returns_If_Dependency_Could_Be_Provided()
[Test]
public void Yields_Null_If_Dependency_Can_Not_Be_Provided()
{
var injector = new DependencyInjector(DiContext.Current.Container);
var injector = new DependencyInjector(_mockConsumer, _diContext);

injector.TryProvide<IDependency>(out var instance);
injector.TryProvide<IMissingDependency>(out var instance);

Assert.That(instance, Is.Null);
}
}

[TestFixture]
public class SetDependency
public class SetConsumer
{
[Test]
public void Switches_The_Dependency()
{
var injector = new DependencyInjector(DiContext.Current.Container);

injector.SetDependency<IDependency>(new DependencyOne());
injector.SetDependency<IDependency>(new DependencyTwo());

Assert.That(injector.Provide<IDependency>(), Is.InstanceOf<DependencyTwo>());
}
private DiContext _diContext;
private IDependencyConsumer _firstConsumer;
private IDependencyConsumer _secondConsumer;

private interface ISingletonDependency {}
private class SingletonDependency : ISingletonDependency {}

[Test]
public void Throws_Exception_If_Null_Is_Given_As_Parameter()
[SetUp]
public void SetUp()
{
var injector = new DependencyInjector(DiContext.Current.Container);

Assert.Throws<ArgumentNullException>(
() => injector.SetDependency<IDependency>(null)
);
_diContext = new DiContext();
_firstConsumer = MockUtilities.CreateDependencyConsumer(new IDependency[]
{
Dependency.TransientFor<IMockDependency, DependencyOne>()
});
_secondConsumer = MockUtilities.CreateDependencyConsumer(new IDependency[]
{
Dependency.TransientFor<IMockDependency, DependencyTwo>(),
Dependency.SingletonFor<ISingletonDependency, SingletonDependency>()
});
_diContext.RegisterConsumer(_firstConsumer);
_diContext.RegisterConsumer(_secondConsumer);
}
}

[TestFixture]
public class ClearDependency
{
[Test]
public void Removes_Dependency_Instance()
public void Refreshes_Transient_Dependencies()
{
var injector = new DependencyInjector(DiContext.Current.Container);
var injector = new DependencyInjector(_firstConsumer, _diContext);

injector.SetDependency<IDependency>(new DependencyOne());
injector.SetConsumer(_secondConsumer);

injector.ClearDependency<IDependency>();

Assert.Throws<MissingProviderException>(() => injector.Provide<IDependency>());
}

[Test]
public void Does_Not_Throw_If_Dependency_Does_Not_Exist()
{
var injector = new DependencyInjector(DiContext.Current.Container);

Assert.DoesNotThrow(() => injector.ClearDependency<IDependency>());
Assert.That(injector.Provide<IMockDependency>(), Is.InstanceOf<DependencyTwo>());
}
}

[TestFixture]
public class ResetProvidedInstance
public class SetTransientInstance
{
private DiContext _diContext;
private IDependencyConsumer _mockConsumer;

[SetUp]
public void SetUp()
{
DiContext.Current.Container.Clear();
_diContext = new DiContext();
_mockConsumer = MockUtilities.CreateDependencyConsumer(new IDependency[]
{
Dependency.TransientFor<IMockDependency, DependencyOne>()
});
_diContext.RegisterConsumer(_mockConsumer);
}

[Test]
public void Throws_If_No_Default_Dependency_Exists()
{
var injector = new DependencyInjector(DiContext.Current.Container);

Assert.Throws<InvalidOperationException>(
() => injector.ResetProvidedInstance<IDependency>()
);

var initialDependency = new DependencyOne();

injector.SetDependency<IDependency>(initialDependency);

Assert.DoesNotThrow(
() => injector.ResetProvidedInstance<IDependency>()
);
}


[Test]
public void Restores_Singleton_Instance()
public void Switches_A_Transient_Instance()
{
var injector = new DependencyInjector(DiContext.Current.Container);

var singletonInstance = new DependencyOne();
var injector = new DependencyInjector(_mockConsumer, _diContext);

injector.SetDependency<IDependency>(singletonInstance);
injector.SetDependency<IDependency>(new DependencyTwo());
injector.ResetProvidedInstance<IDependency>();

Assert.That(injector.Provide<IDependency>(), Is.SameAs(singletonInstance));
}
injector.SetTransientInstance<IMockDependency>(new DependencyTwo());

[Test]
public void Creates_New_Transient_Instance()
{
var injector = new DependencyInjector(DiContext.Current.Container);
var transientInstance = new DependencyOne();

injector.SetDependency<IDependency>(transientInstance, Scope.Transient);
injector.SetDependency<IDependency>(new DependencyTwo());
injector.ResetProvidedInstance<IDependency>();

Assert.That(injector.Provide<IDependency>(), Is.InstanceOf<DependencyOne>());
Assert.That(injector.Provide<IDependency>(), Is.Not.SameAs(transientInstance));
Assert.That(injector.Provide<IMockDependency>(), Is.InstanceOf<DependencyTwo>());
}
}
}
Expand Down
Loading

0 comments on commit fe70a79

Please sign in to comment.