Reputation: 156459
Using AutoFixture with the AutoFixture.AutoMoq package, I sometimes find tests that weren't configured to correctly test the thing they meant to test, but the problem was never discovered because of the default (Loose) Mock behavior:
public interface IService
{
bool IsSomethingTrue(int id);
}
void Main()
{
var fixture = new Fixture()
.Customize(new AutoMoqCustomization());
var service = fixture.Freeze<Mock<IService>>();
Console.WriteLine(service.Object.IsSomethingTrue(1)); // false
}
I'd like to make Mocks get created with Strict behavior, so we're forced to call Setup()
for the methods we expect to be called. I can do this for each individual mock like this:
fixture.Customize<Mock<IService>>(c => c.FromFactory(() => new Mock<IService>(MockBehavior.Strict)));
But after combing through source code for AutoMoqCustomization() and the various ISpecimenBuilder
and other implementations, I'm pretty lost as to the best way to just make all Mocks get initialized with strict behavior. The framework appears to be very flexible and extensible, so I'm sure there's a simple way to do this--I just can't figure out how.
Upvotes: 11
Views: 2260
Reputation: 111
For those interested, down below you can find @MarkSeemann's reply translated into code. I am pretty sure it does not cover all use cases and it was not heavily tested. But it should be a good starting point.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using Ploeh.AutoFixture;
using Ploeh.AutoFixture.AutoMoq;
using Ploeh.AutoFixture.Kernel;
namespace ConsoleApplication1
{
public class StrictAutoMoqCustomization : ICustomization
{
public StrictAutoMoqCustomization() : this(new MockRelay()) { }
public StrictAutoMoqCustomization(ISpecimenBuilder relay)
{
// TODO Null check params
Relay = relay;
}
public ISpecimenBuilder Relay { get; }
public void Customize(IFixture fixture)
{
// TODO Null check params
fixture.Customizations.Add(new MockPostprocessor(new MethodInvoker(new StrictMockConstructorQuery())));
fixture.ResidueCollectors.Add(Relay);
}
}
public class StrictMockConstructorMethod : IMethod
{
private readonly ConstructorInfo ctor;
private readonly ParameterInfo[] paramInfos;
public StrictMockConstructorMethod(ConstructorInfo ctor, ParameterInfo[] paramInfos)
{
// TODO Null check params
this.ctor = ctor;
this.paramInfos = paramInfos;
}
public IEnumerable<ParameterInfo> Parameters => paramInfos;
public object Invoke(IEnumerable<object> parameters) => ctor.Invoke(parameters?.ToArray() ?? new object[] { });
}
public class StrictMockConstructorQuery : IMethodQuery
{
public IEnumerable<IMethod> SelectMethods(Type type)
{
if (!IsMock(type))
{
return Enumerable.Empty<IMethod>();
}
if (!GetMockedType(type).IsInterface && !IsDelegate(type))
{
return Enumerable.Empty<IMethod>();
}
var ctor = type.GetConstructor(new[] { typeof(MockBehavior) });
return new IMethod[]
{
new StrictMockConstructorMethod(ctor, ctor.GetParameters())
};
}
private static bool IsMock(Type type)
{
return type != null && type.IsGenericType && typeof(Mock<>).IsAssignableFrom(type.GetGenericTypeDefinition()) && !GetMockedType(type).IsGenericParameter;
}
private static Type GetMockedType(Type type)
{
return type.GetGenericArguments().Single();
}
internal static bool IsDelegate(Type type)
{
return typeof(MulticastDelegate).IsAssignableFrom(type.BaseType);
}
}
}
Usage
var fixture = new Fixture().Customize(new StrictAutoMoqCustomization());
Upvotes: 3
Reputation: 233125
There's no simple built-in feature that will enable you to do something like that, but it shouldn't be that hard to do.
Essentially, you'd need to change MockConstructorQuery
so that it invokes the constructor that takes a MockBehavior
value, and pass in MockBehavior.Strict
.
Now, you can't change that behaviour in MockConstructorQuery
, but that class is only some 9-10 lines of code, so you should be able to create a new class that implements IMethodQuery
by using MockConstructorQuery
as a starting point.
Likewise, you'll also need to create a custom ICustomization
that does almost exactly the same as AutoMoqCustomization, with the only exception that it uses your custom IMethodQuery
with strict mock configuration instead of MockConstructorQuery
. That's another 7 lines of code you'll need to write.
All that said, in my experience, using strict mocks is a bad idea. It'll make your tests brittle, and you'll waste a lot of time mending 'broken' tests. I can only recommend that you don't do this, but now I've warned you; it's your foot.
Upvotes: 9