Skip to content

Commit

Permalink
Merge pull request #7 from chickensoft-games/feat/fake-dependencies
Browse files Browse the repository at this point in the history
feat: allow dependencies to be faked
  • Loading branch information
jolexxa authored Oct 18, 2023
2 parents c1225a8 + ed50af4 commit 4548334
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 5 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"version": "0.2.0",
"configurations": [
// For these launch configurations to work, you need to setup a GODOT4
// For these launch configurations to work, you need to setup a GODOT
// environment variable. On mac or linux, this can be done by adding
// the following to your .zshrc, .bashrc, or .bash_profile file:
// export GODOT4="/Applications/Godot.app/Contents/MacOS/Godot"
// export GODOT="/Applications/Godot.app/Contents/MacOS/Godot"
{
"name": "🧪 Debug Tests",
"type": "coreclr",
Expand Down
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
{
"label": "build-solutions",
"group": "test",
"command": "dotnet restore; ${env:GODOT4} --headless --build-solutions --quit || exit 0",
"command": "dotnet restore; ${env:GODOT} --headless --build-solutions --quit || exit 0",
"type": "shell",
"options": {
"cwd": "${workspaceFolder}/Chickensoft.AutoInject.Tests"
Expand Down
4 changes: 2 additions & 2 deletions Chickensoft.AutoInject.Tests/coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# To collect code coverage, you will need the following environment setup:
#
# - A "GODOT4" environment variable pointing to the Godot executable
# - A "GODOT" environment variable pointing to the Godot executable
# - ReportGenerator installed
#
# dotnet tool install -g dotnet-reportgenerator-globaltool
Expand All @@ -26,7 +26,7 @@ dotnet build --no-restore

coverlet \
"./.godot/mono/temp/bin/Debug" --verbosity detailed \
--target $GODOT4 \
--target $GODOT \
--targetargs "--headless --run-tests --coverage --quit-on-finish" \
--format "opencover" \
--output "./coverage/coverage.xml" \
Expand Down
58 changes: 58 additions & 0 deletions Chickensoft.AutoInject.Tests/src/Dependent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ void OnResolved() { }
/// <para>Don't call this method.</para>
/// </summary>
void _AnnounceDependenciesResolved() { }

/// <summary>
/// Add a fake value to the dependency table. Adding a fake value allows a
/// unit test to override a dependency lookup with a fake value.
/// </summary>
/// <param name="value">Dependency value (probably a mock or a fake).</param>
/// <typeparam name="T">Dependency type.</typeparam>
void FakeDependency<T>(T value) where T : notnull;

/// <summary>
/// Returns a dependency that was resolved from an ancestor provider node.
/// </summary>
/// <typeparam name="TValue">The type of the value to resolve.</typeparam>
/// <param name="fallback">Fallback value to use if a provider for this type
/// wasn't found during dependency resolution.</param>
/// <returns>
/// The resolved dependency value, the fallback value, or throws an exception
/// if the provider wasn't found during dependency resolution and a fallback
/// value was not given
/// </returns>
/// <exception cref="ProviderNotFoundException">Thrown if the provider for
/// the requested value could not be found and when no fallback value is
/// specified.</exception>
TValue DependOn<TValue>(Func<TValue>? fallback = default)
where TValue : notnull;
}

/// <summary>
Expand Down Expand Up @@ -139,6 +164,18 @@ public void _AnnounceDependenciesResolved() =>
/// specified.</exception>
public TValue DependOn<TValue>(Func<TValue>? fallback = default)
where TValue : notnull => DependencyResolver.DependOn(this, fallback);


/// <summary>
/// Add a fake value to the dependency table. Adding a fake value allows a
/// unit test to override a dependency lookup with a fake value.
/// </summary>
/// <param name="value">Dependency value (probably a mock or a fake).</param>
/// <typeparam name="T">Dependency type.</typeparam>
public void FakeDependency<T>(T value) where T : notnull {
DependentState.ProviderFakes[typeof(T)] =
new DependencyResolver.DefaultProvider(value);
}
}

/// <summary>
Expand All @@ -164,6 +201,15 @@ public class DependencyState {
/// </summary>
public Dictionary<IProvider, PendingProvider> Pending { get; }
= new();

/// <summary>
/// Overrides for providers keyed by dependency type. Overriding providers
/// allows nodes being unit-tested to provide fake providers during unit tests
/// that return mock or faked values.
/// </summary>
public Dictionary<Type, DependencyResolver.DefaultProvider> ProviderFakes {
get;
} = new();
}

public record PendingProvider(
Expand Down Expand Up @@ -288,6 +334,18 @@ ImmutableDictionary<string, ScriptPropertyOrField> allDependencies
public static TValue DependOn<TValue>(
IDependent dependent, Func<TValue>? fallback = default
) where TValue : notnull {
// First, check dependency fakes. Using a faked value takes priority over
// all the other dependency resolution methods.
if (dependent.DependentState.ProviderFakes.TryGetValue(
typeof(TValue), out var fakeProvider
)
) {
return fakeProvider.Value();
}

// Lookup dependency, per usual, respecting any fallback values if there
// were no resolved providers for the requested type during dependency
// resolution.
if (dependent.DependentState.Dependencies.TryGetValue(
typeof(TValue), out var providerNode
)
Expand Down
17 changes: 17 additions & 0 deletions Chickensoft.AutoInject.Tests/test/src/ResolutionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ public void DependentWithNoDependenciesHasOnResolvedCalled() {
dependent.OnResolvedCalled.ShouldBeTrue();
}

[Test]
public void FakesDependency() {
var dependent = new FakedDependent();

var fakeValue = "I'm fake!";
dependent.FakeDependency(fakeValue);

TestScene.AddChild(dependent);

dependent._Notification((int)Node.NotificationReady);

dependent.OnResolvedCalled.ShouldBeTrue();
dependent.MyDependency.ShouldBe(fakeValue);

TestScene.RemoveChild(dependent);
}

public class BadProvider : IProvider {
public ProviderState ProviderState { get; }

Expand Down
16 changes: 16 additions & 0 deletions Chickensoft.AutoInject.Tests/test/src/subjects/Dependents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ public void OnResolved() {
}
}

[SuperNode(typeof(Dependent))]
public partial class FakedDependent : Node {
public override partial void _Notification(int what);

[Dependency]
public string MyDependency => DependOn(() => "fallback");

public bool OnResolvedCalled { get; private set; }
public string ResolvedValue { get; set; } = "";

public void OnResolved() {
OnResolvedCalled = true;
ResolvedValue = MyDependency;
}
}

[SuperNode(typeof(Dependent))]
public partial class StringDependentFallback : Node {
public override partial void _Notification(int what);
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,30 @@ You can provide fallback values to use when a provider can't be found. This can
public string MyDependency => DependOn<string>(() => "fallback_value");
```

### Faking Dependencies

Sometimes, when testing, you may wish to "fake" the value of a dependency. Faked dependencies take precedence over any providers that may exist above the dependent node, as well as any provided fallback value.

```csharp
[Test]
public void FakesDependency() {
// Some dependent
var dependent = new MyNode();

var fakeValue = "I'm fake!";
dependent.FakeDependency(fakeValue);

TestScene.AddChild(dependent);

dependent._Notification((int)Node.NotificationReady);

dependent.OnResolvedCalled.ShouldBeTrue();
dependent.MyDependency.ShouldBe(fakeValue);

TestScene.RemoveChild(dependent);
}
```

## How AutoInject Works

AutoInject uses a simple, specific algorithm to resolve dependencies.
Expand Down

0 comments on commit 4548334

Please sign in to comment.