始终使用AutoFixture,XUnit和Moq冻结模拟

时间:2014-01-06 17:34:00

标签: c# unit-testing moq xunit.net autofixture

我正在使用AutoFixture,Moq和XUnit扩展程序([Theory]属性),如本博文http://blog.ploeh.dk/2010/10/08/AutoDataTheorieswithAutoFixture中所述。

我注意到大多数单元测试都是这样的:

[Theory, AutoMoqData]
public void Test(
    [Frozen] Mock<IServiceOne> serviceOne,
    [Frozen] Mock<IServiceTwo> serviceTwo,

    MyClass classUnderTest)
{
    // Arrange
    serviceOne
        .Setup(m => m.Get(It.IsAny<int>()));

    serviceTwo
        .Setup(m => m.Delete(It.IsAny<int>()));

    // MyClass has a constructor with arguments for IServiceOne, and IServiceTwo
    // classUnderTest will use the two mocks specified above

    // Act
    var result = classUnderTest.Foo();

    // Assert
    Assert.True(result);
}

与总是使用[Frozen]装饰模具相反,是否有办法设置夹具以始终冻结模具?

以下是AutoMoqData属性:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }
}

2 个答案:

答案 0 :(得分:8)

虽然它目前不是内置的,但是编写一个通用的装饰器很容易,当它们离开AutoFixture责任树时冻结对象:

public class MemoizingBuilder : ISpecimenBuilder
{
    private readonly ISpecimenBuilder builder;
    private readonly ConcurrentDictionary<object, object> instances;

    public MemoizingBuilder(ISpecimenBuilder builder)
    {
        this.builder = builder;
        this.instances = new ConcurrentDictionary<object, object>();
    }

    public object Create(object request, ISpecimenContext context)
    {
        return this.instances.GetOrAdd(
            request,
            r => this.builder.Create(r, context));
    }
}

请注意,它会装饰另一个ISpecimenBuilder,但会在返回之前记住所有值。如果同一请求再次到达,它将返回记忆值。

虽然你不能扩展 AutoMoqCustomization,但你可以复制它的作用(它只有两行代码),并使用它周围的MemoizingBuilder

public class AutoFreezeMoq : ICustomization
{
    public void Customize(IFixture fixture)
    {
        if (fixture == null)
            throw new ArgumentNullException("fixture");

        fixture.Customizations.Add(
            new MemoizingBuilder(
                new MockPostprocessor(
                    new MethodInvoker(
                        new MockConstructorQuery()))));
        fixture.ResidueCollectors.Add(new MockRelay());
    }
}

使用此AutoFreezeMoq代替AutoMoqCustomization。它将冻结所有模拟,以及从这些模拟创建的所有接口和抽象基类。

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoFreezeMoq()))
    {
    }
}

答案 1 :(得分:3)

我最终copying code from the AutoDataAttribute class并修改它以包含FreezingCustomization

这是结果属性。

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }

    public override IEnumerable<object[]> GetData(System.Reflection.MethodInfo methodUnderTest, Type[] parameterTypes)
    {
        var specimens = new List<object>();
        foreach (var p in methodUnderTest.GetParameters())
        {
            CustomizeFixture(p);
            if (p.ParameterType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IMock<>)))
            {
                var freeze = new FreezingCustomization(p.ParameterType, p.ParameterType);
                this.Fixture.Customize(freeze);
            }
            var specimen = Resolve(p);
            specimens.Add(specimen);
        }

        return new[] { specimens.ToArray() };
    }

    private void CustomizeFixture(ParameterInfo p)
    {
        var dummy = false;
        var customizeAttributes = p.GetCustomAttributes(typeof(CustomizeAttribute), dummy).OfType<CustomizeAttribute>();
        foreach (var ca in customizeAttributes)
        {
            var c = ca.GetCustomization(p);
            this.Fixture.Customize(c);
        }
    }

    private object Resolve(ParameterInfo p)
    {
        var context = new SpecimenContext(this.Fixture);
        return context.Resolve(p);
    }
}