Created
October 27, 2021 17:19
-
-
Save randyburden/bddc1b852372ae5e7fff76282a5e7c6f to your computer and use it in GitHub Desktop.
C# Method Interception and Redirect Extension Methods and Examples using the MonoMod.RuntimeDetour library. Useful for testing hard to test code, libraries, static methods, simulating exceptions in deep call stacks of old code or libraries, etc. Keywords: C# test static methods, telerik justmock alternative, typemock alternative, typemock isolat…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using MonoMod.RuntimeDetour; // <PackageReference Include="MonoMod.RuntimeDetour" Version="21.1.11.1" /> | |
using NUnit.Framework; | |
using System; | |
using System.Reflection; | |
namespace MethodInterceptionHelper | |
{ | |
public static class MethodExtensions | |
{ | |
/// <summary> | |
/// Redirect method calls from origin method to the target method. | |
/// This allows you to mock and test *any* method including static methods, methods in other libraries, etc. | |
/// Always wrap in a using statement so that the operation is undone. | |
/// </summary> | |
/// <param name="origin">Original method to swap.</param> | |
/// <param name="target">Target method to redirect to.</param> | |
public static MethodRedirectOperation RedirectTo(this MethodInfo origin, MethodInfo target) | |
{ | |
return new MethodRedirectOperation(origin, target); | |
} | |
} | |
public class MethodRedirectOperation : IDisposable | |
{ | |
private readonly Detour _detour; | |
public MethodRedirectOperation(MethodInfo origin, MethodInfo target) | |
{ | |
_detour = new Detour(origin, target); | |
} | |
public void Dispose() | |
{ | |
_detour?.Dispose(); | |
} | |
} | |
[TestFixture] | |
[Explicit] | |
public class MethodRedirectOperationTests | |
{ | |
[Test] | |
[Explicit] | |
public static void How_To_Redirect_A_Property_Get_Method() | |
{ | |
// Arrange | |
var testObject = new TestObject(); | |
var from = typeof(TestObject).GetProperty(nameof(TestObject.IsValid))?.GetMethod; | |
var to = new Func<bool>(() => | |
{ | |
// Put a breakpoint here to see that it gets called | |
return true; | |
}); | |
using (from.RedirectTo(to.Method)) | |
{ | |
// Act | |
var value = testObject.IsValid; | |
// Assert | |
Assert.That(value, Is.True); | |
} | |
} | |
[Test] | |
[Explicit] | |
public static void How_To_Redirect_A_Method() | |
{ | |
// Arrange | |
var from = typeof(MethodRedirectOperationTests).GetMethod(nameof(MethodRedirectOperationTests.GetTestObject)); | |
var to = new Func<TestObject>(() => | |
{ | |
// Put a breakpoint here to see that it gets called | |
return new TestObject | |
{ | |
SomeString = "A different string", | |
SomeInteger = 500 | |
}; | |
}); | |
using (from.RedirectTo(to.Method)) | |
{ | |
// Act | |
var value = new MethodRedirectOperationTests().GetTestObject(); | |
// Assert | |
Assert.That(value, Is.Not.Null); | |
Assert.That(value.SomeString, Is.EqualTo("A different string")); | |
Assert.That(value.SomeInteger, Is.EqualTo(500)); | |
} | |
} | |
public TestObject GetTestObject() | |
{ | |
return new TestObject | |
{ | |
SomeString = "Some String", | |
SomeInteger = 1 | |
}; | |
} | |
public class TestObject | |
{ | |
public string SomeString { get; set; } | |
public int SomeInteger { get; set; } | |
public bool IsValid => !string.IsNullOrWhiteSpace(SomeString) && SomeInteger != 0; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment