Skip to content

Commit

Permalink
feat(DependencyInjector): add RestoreDefaultDependency method
Browse files Browse the repository at this point in the history
  • Loading branch information
jonisavo committed Jun 5, 2022
1 parent c9dc0ad commit ef1952f
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 16 deletions.
15 changes: 15 additions & 0 deletions Assets/UIComponents.Tests/DependencyInjectorStaticTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,20 @@ public void Removes_The_Injector_From_The_Dependency_Injector()
Assert.That(DependencyInjector.InjectorDictionary.ContainsKey(componentType), Is.False);
}
}

[TestFixture]
public class RestoreDefaultDependency
{
[Test]
public void Restores_The_Default_Dependency_Instance()
{
var component = new UIComponentWithDependency();

DependencyInjector.ClearDependency<UIComponentWithDependency, IDependency>();
DependencyInjector.RestoreDefaultDependency<UIComponentWithDependency, IDependency>();

Assert.That(component.GetDependency(), Is.InstanceOf<DependencyProvider>());
}
}
}
}
37 changes: 37 additions & 0 deletions Assets/UIComponents.Tests/DependencyInjectorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,42 @@ public void Does_Not_Throw_If_Dependency_Does_Not_Exist()
Assert.DoesNotThrow(() => injector.ClearDependency<IDependency>());
}
}

[TestFixture]
public class RestoreDefaultDependency
{
[Test]
public void Restores_Default_Dependency()
{
var dependencyAttribute =
new DependencyAttribute(typeof(IDependency), typeof(DependencyOne));

var injector = new DependencyInjector(new[] {dependencyAttribute});

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

Assert.That(injector.Provide<IDependency>(), Is.InstanceOf<DependencyTwo>());

injector.RestoreDefaultDependency<IDependency>();

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

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

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

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

Assert.Throws<InvalidOperationException>(
() => injector.RestoreDefaultDependency<IDependency>()
);
}
}
}
}
52 changes: 49 additions & 3 deletions Assets/UIComponents/Core/DependencyInjector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ internal static readonly Dictionary<Type, object> InstantiatedInstanceDictionary
/// </summary>
internal readonly Dictionary<Type, object> DependencyDictionary
= new Dictionary<Type, object>();

/// <summary>
/// Contains the default provider types for each dependency of the consumer.
/// Populated by <see cref="DependencyAttribute"/>.
/// </summary>
internal readonly Dictionary<Type, Type> DefaultDependencyTypeDictionary
= new Dictionary<Type, Type>();

/// <summary>
/// Switches the dependency of a consumer.
Expand Down Expand Up @@ -69,6 +76,24 @@ public static void ClearDependency<TConsumer, TDependency>()

injector.ClearDependency<TDependency>();
}

/// <summary>
/// Restores the default dependency, which is the one
/// set by <see cref="DependencyAttribute"/>.
/// </summary>
/// <typeparam name="TConsumer">Consumer type</typeparam>
/// <typeparam name="TDependency">Dependency type</typeparam>
/// <exception cref="InvalidOperationException">
/// Thrown if no default dependency type exists
/// </exception>
public static void RestoreDefaultDependency<TConsumer, TDependency>()
where TConsumer : class
where TDependency : class
{
var injector = GetInjector(typeof(TConsumer));

injector.RestoreDefaultDependency<TDependency>();
}

/// <summary>
/// Returns the injector of the given consumer type.
Expand Down Expand Up @@ -136,11 +161,14 @@ public DependencyInjector(IEnumerable<DependencyAttribute> dependencyAttributes)
{
foreach (var dependencyAttribute in dependencyAttributes)
{
var type = dependencyAttribute.DependencyType;
var dependencyType = dependencyAttribute.DependencyType;
var providerType = dependencyAttribute.ProvideType;

if (DependencyDictionary.ContainsKey(dependencyType))
return;

if (!DependencyDictionary.ContainsKey(type))
DependencyDictionary[type] = CreateInstance(providerType);
DependencyDictionary[dependencyType] = CreateInstance(providerType);
DefaultDependencyTypeDictionary[dependencyType] = providerType;
}
}

Expand Down Expand Up @@ -169,6 +197,24 @@ public void ClearDependency<T>() where T : class
DependencyDictionary.Remove(typeof(T));
}

/// <summary>
/// Restores the default dependency, which is the one
/// set by <see cref="DependencyAttribute"/>.
/// </summary>
/// <typeparam name="T">Dependency type</typeparam>
/// <exception cref="InvalidOperationException">
/// Thrown if no default dependency type exists
/// </exception>
public void RestoreDefaultDependency<T>() where T : class
{
var dependencyType = typeof(T);

if (!DefaultDependencyTypeDictionary.TryGetValue(dependencyType, out var providerType))
throw new InvalidOperationException($"No default dependency type for {dependencyType}");

DependencyDictionary[typeof(T)] = CreateInstance(providerType);
}

/// <summary>
/// Returns a dependency. Throws a <see cref="MissingProviderException"/>
/// if the dependency can not be provided.
Expand Down
49 changes: 36 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ public class OtherComponent : MyComponent {}

### Testing

#### Switching dependencies

`DependencyInjector`, the class responsible for handling dependencies,
comes with the `SetDependency` static method.

Expand All @@ -292,19 +294,8 @@ A mock version of a dependency can be switched in during a test. When `CounterCo
asks for `ICounterService`, it will receive the instance of `MockCounterService` created
in the `OneTimeSetUp` function.

`DependencyInjector` also comes with the `ClearDependency` static method which can be used to remove
the instance of a dependency between tests.

```c#
[TearDown]
public void TearDown()
{
DependencyInjector.ClearDependency<CounterComponent, ICounterService>();
// Provide<ICounterService> inside CounterComponent will now throw a MissingProviderException
}
```

A `DependencyScope` helper class is available under the `UIComponents.Utilities` namespace.
The `DependencyScope` helper class is available under the `UIComponents.Utilities` namespace.
It is useful for switching dependencies temporarily.

```c#
[Dependency(typeof(ICounterService), provide: typeof(CounterService))]
Expand All @@ -326,6 +317,38 @@ public void It_Works()
}
```

#### Clearing dependencies

`DependencyInjector` also comes with the `ClearDependency` static method which can be used to remove
the instance of a dependency between tests.

```c#
[TearDown]
public void TearDown()
{
DependencyInjector.ClearDependency<CounterComponent, ICounterService>();
// Provide<ICounterService> inside CounterComponent will now throw a MissingProviderException
}
```

#### Restoring dependencies

Use `RestoreDefaultDependency` to restore the dependency to its original value as
specified in the `[Dependency]` attribute.

```c#
[TearDown]
public void TearDown()
{
DependencyInjector.RestoreDefaultDependency<CounterComponent, ICounterService>();
// Provide<ICounterService> inside CounterComponent will now yield
// an instance of the type specified in the component's Dependency attribute.
}
```

If no `[Dependency]` attribute exists on the component, `RestoreDefaultDependency` will throw
an exception.

## Loading assets

### Resources
Expand Down

0 comments on commit ef1952f

Please sign in to comment.