Created
July 26, 2019 22:26
-
-
Save Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b to your computer and use it in GitHub Desktop.
Moq extension to cleanly make assertions about arguments passed to mock dependencies
This file contains hidden or 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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Moq; | |
using Xunit; | |
namespace UnitTests | |
{ | |
public class ExampleTest | |
{ | |
[Fact(DisplayName = "HandleThing should massage the data and persist it")] | |
public async Task HandleThing() | |
{ | |
var fakePersistedThing = new OutThing(); | |
var mockDependency = new Mock<IExampleDependency>(); | |
mockDependency | |
.Setup(m => m.PersistThings(It.IsAny<InThing2>(), It.IsAny<InThing3>())) | |
.ReturnsAsync(fakePersistedThing); | |
var unit = new ExampleUnit(mockDependency.Object); | |
var inThing1 = new InThing1 | |
{ | |
Prop1 = "Input Data", | |
Prop2 = " I need a trim", | |
Prop3 = "I nEeD tO bE uPpeR cAsEd" | |
}; | |
var result = await unit.HandleThing(inThing1); | |
Assert.Equal(fakePersistedThing, result); | |
mockDependency | |
.CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings)) | |
.WithArg<InThing2>(inThing2 => | |
{ | |
Assert.Equal("Input Data with Important additional data", inThing2.Prop1); | |
Assert.Equal("I need a trim", inThing2.Prop2); | |
}) | |
.AndArg<InThing3>(inThing3 => | |
{ | |
Assert.Equal("Important Default Value", inThing3.Prop1); | |
Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2); | |
}); | |
} | |
} | |
} |
This file contains hidden or 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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace UnitTests | |
{ | |
public class ExampleUnit | |
{ | |
private IExampleDependency _dependency; | |
public ExampleUnit(IExampleDependency dependency) | |
{ | |
_dependency = dependency; | |
} | |
public async Task<OutThing> HandleThing(InThing1 inThing1) | |
{ | |
var inThing2 = new InThing2() | |
{ | |
Prop1 = inThing1.Prop1 + " with Important additional data", | |
Prop2 = inThing1.Prop2.Trim() | |
}; | |
var inThing3 = new InThing3() | |
{ | |
Prop1 = "Important Default Value", | |
Prop2 = inThing1.Prop3.ToUpper() | |
}; | |
return await _dependency.PersistThings(inThing2, inThing3); | |
} | |
} | |
public interface IExampleDependency | |
{ | |
Task<OutThing> PersistThings(InThing2 inThing2, InThing3 inThing3); | |
} | |
public class InThing1 | |
{ | |
public string Prop1; | |
public string Prop2; | |
public string Prop3; | |
} | |
public class InThing2 | |
{ | |
public string Prop1; | |
public string Prop2; | |
} | |
public class InThing3 | |
{ | |
public string Prop1; | |
public string Prop2; | |
} | |
public class OutThing | |
{ | |
} | |
} |
This file contains hidden or 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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Moq; | |
using Xunit; | |
namespace UnitTests | |
{ | |
public static class MoqExtensions | |
{ | |
public static IInvocation CheckMethodWasCalledOnce(this Mock mock, string methodName) | |
{ | |
var methodsMatchingNameOnMock = mock.Object.GetType().GetMethods().Where(method => method.Name == methodName); | |
Assert.True(methodsMatchingNameOnMock.Count() > 0, | |
$"No methods with the name {methodName} were found on the mock {mock}"); | |
Assert.True(methodsMatchingNameOnMock.Count() == 1, | |
$"More than one {methodName} on mock {mock}. This utility doens't support method overloading, use something else for that"); | |
var calls = mock.Invocations.Where(invocation => invocation.Method.Name == methodName); | |
Assert.True(calls.Count() == 1, | |
$"Expected {methodName} on mock {mock} to be called once, " + | |
$"but it was called {calls.Count()} times. Use something else if you have 'not called' or 'called multiple times' scenarios!"); | |
return calls.First(); | |
} | |
public static IInvocation WithArg<T>(this IInvocation invocation, Action<T> argumentAssertions) | |
{ | |
var matchingArgumentsOfType = invocation.Arguments | |
.Where(arg => arg.GetType().IsAssignableFrom(typeof(T))) | |
.Select(arg => (T)arg); | |
Assert.True(matchingArgumentsOfType.Count() > 0, | |
$"Method {invocation.Method.Name} on mock [{invocation.Method.DeclaringType.Name}] " + | |
$"has no matching arguments of type {typeof(T).Name}. " + | |
$"Probably the method signature changed, double check it still has a parameter you're looking for"); | |
Assert.True(matchingArgumentsOfType.Count() == 1, | |
$"Method {invocation.Method.Name} on mock [{invocation.Method.DeclaringType.Name}] " + | |
$"has multiple matching arguments of type {typeof(T).Name}. " + | |
$"Use something else if you'r method signature has multiple "); | |
argumentAssertions(matchingArgumentsOfType.First()); | |
return invocation; | |
} | |
public static IInvocation AndArg<T>(this IInvocation invocation, Action<T> argumentAssertions) | |
{ | |
return invocation.WithArg(argumentAssertions); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment