How can I make AutoMoqCustomization use Strict MockBehavior?
Asked Answered
A

2

11

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.

Abjure answered 3/12, 2015 at 0:23 Comment(0)
B
9

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.

Brooder answered 3/12, 2015 at 20:37 Comment(4)
Thanks for the feedback. I'll see if I can make it work. Regarding strict mocks: if the mocks were truly "mocks", I think your point is valid. Unfortunately, most of my mocks actually serve the purpose of stubs, which are expected to return a value. If the SUT relies on data returned from the stub, and I haven't set up the stub, then in the best case the test will fail, and in the worst case the test will pass for the wrong reason. I'd prefer, over either case, to instantly see the line of code showing which stub needs configuring, rather than having to deduce where the NRE is coming from.Abjure
@Abjure blog.ploeh.dk/2013/10/23/mocks-for-commands-stubs-for-queriesBrooder
I find that article's example to be pretty contrived: breaking CQS to create and return a user object that doesn't even have the ID provided to the GetUser method. Those aren't the kinds of changes that I typically see being made to methods. The changes I do see regularly would necessitate a change in the unit test anyway, even though they're written like the final correct example in that article. When that happens, I find that Strict mocks' fail-fast behavior saves a lot of time and--more importantly--helps to ensure my unit tests are testing what they claim to be testing.Abjure
@MarkSeemann Unfortunately, Moq does not distinguish Queries and Commands. Having strict on Queries is ok, IMO github.com/moq/moq4/issues/1056Stormy
B
3

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());
Bluepencil answered 27/4, 2016 at 4:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.