10

将 AutoFixture 与 AutoFixture.AutoMoq 包一起使用,我有时会发现未配置为正确测试他们要测试的东西的测试,但由于默认的(松散)模拟行为,从未发现问题:

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
}

我想让 Mocks 使用严格的行为创建,所以我们不得不调用Setup()我们期望调用的方法。我可以像这样为每个单独的模拟执行此操作:

fixture.Customize<Mock<IService>>(c => c.FromFactory(() => new Mock<IService>(MockBehavior.Strict)));

但是在梳理了 AutoMoqCustomization() 的源代码以及各种ISpecimenBuilder和其他实现之后,我对让所有 Mocks 以严格行为初始化的最佳方法感到非常迷茫。该框架似乎非常灵活和可扩展,所以我确信有一种简单的方法可以做到这一点——我只是不知道怎么做。

4

2 回答 2

7

没有简单的内置功能可以让你做这样的事情,但做起来应该不难

本质上,您需要进行更改MockConstructorQuery,以便它调用接受MockBehavior值的构造函数,并传入MockBehavior.Strict.

现在,您无法在 中更改该行为MockConstructorQuery,但该类只有大约 9-10 行代码,因此您应该能够创建一个IMethodQuery以使用MockConstructorQuery为起点来实现的新类。

同样,您还需要创建一个与 AutoMoqCustomization 几乎完全相同的自定义ICustomization唯一的例外是它使用IMethodQuery具有严格模拟配置的自定义而不是MockConstructorQuery. 那是您需要编写的另外 7 行代码。

综上所述,根据我的经验,使用严格的模拟是一个坏主意。它会使您的测试变得脆弱,并且您会浪费大量时间来修补“损坏”的测试。我只能建议你不要这样做,但现在我已经警告你了;这是你的脚。

于 2015-12-03T20:37:45.750 回答
3

对于那些感兴趣的人,您可以在下面找到@MarkSeemann 的回复翻译成代码。我很确定它没有涵盖所有用例,并且没有经过大量测试。但这应该是一个很好的起点。

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);
        }
    }
}

用法

var fixture = new Fixture().Customize(new StrictAutoMoqCustomization());
于 2016-04-27T04:54:53.107 回答