diff --git a/src/DivertR/ISpy.cs b/src/DivertR/ISpy.cs
index dedc569c..62aab9b4 100644
--- a/src/DivertR/ISpy.cs
+++ b/src/DivertR/ISpy.cs
@@ -11,7 +11,7 @@ namespace DivertR
///
/// inherits from and therefore its Mock behaviour can be configured using the same Redirect fluent interface e.g. to add one or more instances or reset.
/// The Spy is preconfigured with a Via that records the mock object calls readable from the property.
- /// As with a normal Redirect, Spy reset removes all Vias, however it also adds a new Mock call record Via and the property is replaced.
+ /// Spy.Reset behaves the sames as the base Redirect class and removes all Vias except a new call record Via is then added back. Reset also sets a new property.
///
public interface ISpy : IRedirect
{
@@ -26,7 +26,7 @@ public interface ISpy : IRedirect
IRecordStream Calls { get; }
///
- /// Insert a into this Spy.
+ /// Insert an into this Spy.
///
/// The Via instance to insert.
/// Optional action.
diff --git a/src/DivertR/Internal/RedirectTracker.cs b/src/DivertR/Internal/RedirectTracker.cs
new file mode 100644
index 00000000..f724e862
--- /dev/null
+++ b/src/DivertR/Internal/RedirectTracker.cs
@@ -0,0 +1,30 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace DivertR.Internal
+{
+ internal class RedirectTracker
+ {
+ private readonly ConditionalWeakTable _redirectTable = new();
+
+ public void AddRedirect(Redirect redirect, [DisallowNull] TTarget proxy) where TTarget : class?
+ {
+ _redirectTable.Add(proxy, redirect);
+ }
+
+ public Redirect GetRedirect([DisallowNull] TTarget proxy) where TTarget : class?
+ {
+ if (!_redirectTable.TryGetValue(proxy, out var redirect))
+ {
+ throw new DiverterException("Redirect not found");
+ }
+
+ if (redirect is not Redirect redirectOf)
+ {
+ throw new DiverterException($"Redirect target type: {redirect.RedirectId.Type} does not match proxy type: {typeof(TTarget)}");
+ }
+
+ return redirectOf;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DivertR/Internal/SpyTracker.cs b/src/DivertR/Internal/SpyTracker.cs
deleted file mode 100644
index 0463255f..00000000
--- a/src/DivertR/Internal/SpyTracker.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-
-namespace DivertR.Internal
-{
- internal class SpyTracker
- {
- private readonly ConditionalWeakTable _spyTable = new();
-
- public void AddSpy(Spy spy, [DisallowNull] TTarget mock) where TTarget : class?
- {
- _spyTable.Add(mock, spy);
- }
-
- public Spy GetSpy([DisallowNull] TTarget mock) where TTarget : class?
- {
- if (!_spyTable.TryGetValue(mock, out var spy))
- {
- throw new DiverterException("Spy not found");
- }
-
- if (spy is not Spy spyOf)
- {
- throw new DiverterException($"Spy target type: {spy.RedirectId.Type} does not match mock type: {typeof(TTarget)}");
- }
-
- return spyOf;
- }
- }
-}
\ No newline at end of file
diff --git a/src/DivertR/Redirect.cs b/src/DivertR/Redirect.cs
index 9cf4e9ef..8ba71a48 100644
--- a/src/DivertR/Redirect.cs
+++ b/src/DivertR/Redirect.cs
@@ -114,10 +114,19 @@ public TTarget Proxy(TTarget? root)
{
if (root is null || !RedirectSet.Settings.CacheRedirectProxies)
{
- return _proxyFactory.CreateProxy(_redirectProxyCall, root);
+ var createdProxy = _proxyFactory.CreateProxy(_redirectProxyCall, root);
+ Redirect.Track(this, createdProxy);
+
+ return createdProxy;
}
- var proxy = _proxyCache.GetValue(root, x => _proxyFactory.CreateProxy(_redirectProxyCall, x));
+ var proxy = _proxyCache.GetValue(root, x =>
+ {
+ var createdProxy = _proxyFactory.CreateProxy(_redirectProxyCall, x);
+ Redirect.Track(this, createdProxy);
+
+ return createdProxy;
+ });
return proxy!;
}
@@ -250,4 +259,53 @@ protected virtual void ResetInternal()
RedirectRepository.Reset();
}
}
+
+ ///
+ /// Redirect helper class for creating proxy objects directly.
+ ///
+ public static class Redirect
+ {
+ private static readonly RedirectTracker RedirectTracker = new();
+
+ ///
+ /// Creates and returns a proxy of the given target type.
+ ///
+ /// The proxy target type.
+ /// The created Redirect proxy object.
+ public static TTarget Proxy() where TTarget : class?
+ {
+ var redirect = new Redirect();
+
+ return redirect.Proxy();
+ }
+
+ ///
+ /// Creates and returns a proxy of the given target type.
+ ///
+ /// /// The proxy target type.
+ /// The root instance the proxy will wrap and relay calls to.
+ /// The created Redirect proxy object.
+ public static TTarget Proxy(TTarget? root) where TTarget : class?
+ {
+ var redirect = new Redirect();
+
+ return redirect.Proxy(root);
+ }
+
+ ///
+ /// Gets the of the proxy object.
+ ///
+ /// The Redirect proxy object.
+ /// The Redirect instance.
+ /// Thrown if if the given object does not have an associated
+ public static IRedirect Of([DisallowNull] TTarget proxy) where TTarget : class?
+ {
+ return RedirectTracker.GetRedirect(proxy);
+ }
+
+ internal static void Track(Redirect redirect, [DisallowNull] TTarget proxy) where TTarget : class?
+ {
+ RedirectTracker.AddRedirect(redirect, proxy);
+ }
+ }
}
diff --git a/src/DivertR/RedirectId.cs b/src/DivertR/RedirectId.cs
index df4f251e..8394758b 100644
--- a/src/DivertR/RedirectId.cs
+++ b/src/DivertR/RedirectId.cs
@@ -34,7 +34,7 @@ public bool Equals(RedirectId other)
return _id.Equals(other._id);
}
- public override bool Equals(object obj)
+ public override bool Equals(object? obj)
{
return obj is RedirectId other && Equals(other);
}
diff --git a/src/DivertR/Spy.cs b/src/DivertR/Spy.cs
index dbf300f9..5a51f33a 100644
--- a/src/DivertR/Spy.cs
+++ b/src/DivertR/Spy.cs
@@ -25,7 +25,6 @@ private Spy(DiverterSettings? diverterSettings, TTarget? root, bool hasRoot)
: base(diverterSettings)
{
Mock = hasRoot ? Proxy() : Proxy(root);
- Spy.AddSpy(this, Mock);
ResetAndConfigureRecord();
}
@@ -134,8 +133,6 @@ private RecordStream CallsLocked
///
public static class Spy
{
- private static readonly SpyTracker SpyTracker = new();
-
///
/// Creates a spy of the given target type and returns its mock object.
///
@@ -167,12 +164,14 @@ public static TTarget On(TTarget? root) where TTarget : class?
/// Thrown if if the given object does not have an associated
public static ISpy Of([DisallowNull] TTarget mock) where TTarget : class?
{
- return SpyTracker.GetSpy(mock);
- }
-
- internal static void AddSpy(Spy spy, [DisallowNull] TTarget mock) where TTarget : class?
- {
- SpyTracker.AddSpy(spy, mock);
+ var redirect = Redirect.Of(mock);
+
+ if (redirect is not Spy spyOf)
+ {
+ throw new DiverterException("Spy not found");
+ }
+
+ return spyOf;
}
}
}
\ No newline at end of file
diff --git a/test/DivertR.UnitTests/QuickstartExamples.cs b/test/DivertR.UnitTests/QuickstartExamples.cs
index 394d83d8..9146daa3 100644
--- a/test/DivertR.UnitTests/QuickstartExamples.cs
+++ b/test/DivertR.UnitTests/QuickstartExamples.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using System.Threading.Tasks;
using DivertR.DependencyInjection;
using DivertR.UnitTests.Model;
diff --git a/test/DivertR.UnitTests/RedirectStaticTests.cs b/test/DivertR.UnitTests/RedirectStaticTests.cs
new file mode 100644
index 00000000..d72e48a9
--- /dev/null
+++ b/test/DivertR.UnitTests/RedirectStaticTests.cs
@@ -0,0 +1,90 @@
+using DivertR.UnitTests.Model;
+using Shouldly;
+using Xunit;
+
+namespace DivertR.UnitTests;
+
+public class RedirectStaticTests
+{
+ [Fact]
+ public void GivenProxy_WhenProxyMethodCalled_ThenRelayToRoot()
+ {
+ // ARRANGE
+ var proxy = Redirect.Proxy(new Foo("test"));
+
+ // ACT
+ var callResult = proxy.Name;
+
+ // ASSERT
+ callResult.ShouldBe("test");
+ }
+
+ [Fact]
+ public void GivenProxyWithNoRoot_WhenProxyCalled_ShouldReturnDefault()
+ {
+ // ARRANGE
+ var proxy = Redirect.Proxy();
+
+ // ACT
+ var callResult = proxy.Name;
+
+ // ASSERT
+ callResult.ShouldBeNull();
+ }
+
+ [Fact]
+ public void GivenProxyWithVia_WhenMockCalled_ThenRedirects()
+ {
+ // ARRANGE
+ var proxy = Redirect.Proxy();
+ var redirect = Redirect.Of(proxy);
+
+ redirect
+ .To(x => x.Echo(Is.Any))
+ .Via<(string input, __)>(call => $"{call.Args.input} redirected");
+
+ // ACT
+ var result = proxy.Echo("test");
+
+ // ASSERT
+ result.ShouldBe("test redirected");
+ }
+
+ [Fact]
+ public void GivenProxyAsObject_WhenRedirectOf_ThenThrowsDiverterException()
+ {
+ // ARRANGE
+ object fooProxy = Redirect.Proxy();
+
+ // ACT
+ var testAction = () => Redirect.Of(fooProxy);
+
+ // ASSERT
+ testAction.ShouldThrow();
+ }
+
+ [Fact]
+ public void GivenNonProxyObject_WhenRedirectOfObject_ThenThrowsDiverterException()
+ {
+ // ARRANGE
+ IFoo foo = new Foo();
+
+ // ACT
+ var testAction = () => Redirect.Of(foo);
+
+ // ASSERT
+ testAction.ShouldThrow();
+ }
+
+ [Fact]
+ public void GivenDispatchProxyFactory_WhenSpyOnClassType_ThenThrowsDiverterException()
+ {
+ // ARRANGE
+
+ // ACT
+ var testAction = () => Redirect.Proxy();
+
+ // ASSERT
+ testAction.ShouldThrow();
+ }
+}
\ No newline at end of file