Skip to content

Instantly share code, notes, and snippets.

@Jacob-McKay
Created July 26, 2019 22:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b to your computer and use it in GitHub Desktop.
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
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);
});
}
}
}
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
{
}
}
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