All of the methods start from the NosePlug.Nasal class. This class contains the methods needed to create method plugs that intercept method calls. After creating the needed plugs invoke the ApplyAsync() method passing all plugs to be applied. This method will return a scope that is expected to be disposed when the plugs should no longer be applied. Internally this method ensures that only a single plugs is active and intercepting a given static call at a time. Changes made after invoking this method will not take affect.
To intercept a method simply use the Method method on a Nasal instance. The IMethodPlug instance that can be used to specify a delegate to be invoked instead of the static method using the Callback method. This can be used to validate that a method was invoked. If the method is invoked multiple times, only the last delegate is invoked.
public class HasPublicMethod
{
public static void NoParameters() { ... }
}
[Fact]
public async Task ExampleTest()
{
//Arrange
int invocationCount = 0;
var plug = Nasal.Method(() => HasPublicMethod.NoParameters())
.Callback(() => invocationCount++);
using IDisposable _ = await Nasal.ApplyAsync(plug);
//Act
HasPublicMethod.NoParameters();
//Assert
Assert.Equal(1, invocationCount);
}To create a plug for a method that contains parameters, simply specify any value for each of the parameters. The values for these parameters are ignored, and only used to determine with method overload to replace. The delegate passed to Callback can be used to validate the parameters that were passed to the method.
If the method has a return value, a default value based on the return type will be automatically be returned.
public class HasPublicMethod
{
public static void Overloaded(int value) { ... }
public static void Overloaded(string @string, int value) { ... }
}
[Fact]
public async Task ExampleTest()
{
//Arrange
int invocationCount = 0;
string? passedString = null;
int passedValue = 0;
var plug = Nasal.Method(() => HasPublicMethod.Overloaded("", 0))
.Callback((string first, int second) => {
passedString = first;
passedValue = second;
invocationCount++;
});
using IDisposable _ = await Nasal.ApplyAsync(plug);
//Act
HasPublicMethod.Overloaded("Foo", 42);
//Assert
Assert.Equal(1, invocationCount);
Assert.Equal("Foo", passedString);
Assert.Equal(42, passedValue);
}For methods that return values, you can use the Returns method instead of the Callback method. This method allows for returning an alternate value. For async methods that return a Task, you must return a Task instance. Not returning a Task instance will result in a null value being returned, causing any called awaiting the Task to throw a NullReferenceException.
public class HasPublicMethod
{
public static async Task<int> AsyncMethodWithReturn() { ... }
}
[Fact]
public async Task ExampleTest()
{
//Arrange
var plug = Nasal.Method(() => HasPublicMethod.AsyncMethodWithReturn())
.Returns(() => Task.FromResult(42));
using IDisposable _ = await Nasal.ApplyAsync(plug);
//Act
int value = await HasPublicMethod.AsyncMethodWithReturn();
//Assert
Assert.Equal(42, value);
}To intercept a property simply use the Property method on the Nasal class. The IPropertyPlug<T> instance that can be used to specify delegates to be invoked instead of the getter and/or setter methods for the property. To intercept the getter use the Returns method.
public class HasPublicProperty
{
public static Guid Foo { get; set; }
}
[Fact]
public async Task ExampleTest()
{
//Arrange
Guid testGuid = Guid.NewGuid();
var plug = Nasal.Property(() => HasPublicProperty.Foo)
.Returns(() => testGuid);
using IDisposable _ = await Nasal.ApplyAsync(plug);
//Act
Guid value = HasPublicProperty.Foo;
//Assert
Assert.Equal(testGuid, value);
}To intercept the setter use the Callback method to specify a delegate to be invoked instead of the original setter. If this method is called multiple times, only the last delegate will be invoked.
public class HasPublicProperty
{
public static Guid Foo { get; set; }
}
[Fact]
public async Task ExampleTest()
{
//Arrange
Guid testGuid = Guid.NewGuid();
Guid passedGuid = Guid.Empty;
var plug = Nasal.Property(() => HasPublicProperty.Foo)
.Callback(x => passedGuid = x);
using IDisposable _ = await Nasal.ApplyAsync(plug);
//Act
HasPublicProperty.Foo = testGuid;
//Assert
Assert.Equal(testGuid, passedGuid);
Assert.NotEqual(testGuid, HasPublicProperty.Foo);
}There may be cases where invoking the original method, in addition to the replacement delegate, is desired. For both properties and methods, this can be done using the CallOriginal method. Optionally you can specify a boolean indicating if the original method should be invoked. If this method is invoked multiple times, only the last value specified will be used. If the plugged method specifies a return value, the original return value will be used.
public class HasPublicMethod
{
public static int OverloadedValue { get; set; }
public static void Overloaded(int value)
{
OverloadedValue = value;
}
}
[Fact]
public async Task ExampleTest()
{
//Arrange
int invocationCount = 0;
var plug = Nasal.Method(() => HasPublicMethod.Overloaded(0))
.Callback(() => invocationCount++)
.CallOriginal();
using IDisposable _ = await Nasal.ApplyAsync(plug);
HasPublicMethod.OverloadedValue = 0;
//Act
HasPublicMethod.Overloaded(42);
//Assert
Assert.Equal(1, invocationCount);
Assert.Equal(42, HasPublicMethod.OverloadedValue);
}