Always Freeze mocks using AutoFixture, XUnit, and Moq
Asked Answered
S

2

8

I'm using AutoFixture, Moq, and XUnit extensions ([Theory] attribute) as described in this blog post http://blog.ploeh.dk/2010/10/08/AutoDataTheorieswithAutoFixture.

I've noticed that most of unit tests look like this:

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

As opposed to always decorating the mocks with [Frozen], is there a way to setup the fixture to always freeze mocks?

Here's the AutoMoqData attribute:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }
}
Seringapatam answered 6/1, 2014 at 17:33 Comment(0)
C
9

Although it's currently not built-in, it's easy to write a general purpose Decorator that freezes objects as they leave the AutoFixture Tree of Responsibility:

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

Notice that it Decorates another ISpecimenBuilder, but remembers all values before it returns them. If the same request arrives again, it'll return the memoized value.

While you can't extend AutoMoqCustomization, you can replicate what it does (it's only two lines of code), and use the MemoizingBuilder around it:

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

Use this AutoFreezeMoq instead of AutoMoqCustomization. It will freeze all mocks, and all interfaces and abstract base classes created from those mocks.

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoFreezeMoq()))
    {
    }
}
Christie answered 7/1, 2014 at 15:51 Comment(8)
Thank you. Could you create one for NSubstitute? I'm currently evaluating using that over Moq. Here's my attempt: fixture.Customizations.Add( new MemoizingBuilder( new NSubstituteBuilder( new MethodInvoker( new NSubstituteMethodQuery()))));Seringapatam
Looking at AutoNSubstituteCustomization, I think you need to add it to fixture.ResidueCollectors instead of fixture.Customizations, but otherwise it looks right. I haven't tested it, though. Does it work?Christie
It worked when I added it either fixture.ResidueCollectors or fixture.Customizations. Not sure why though.Seringapatam
I noticed that for the NSubstitute setup in my comment, I still need to use [Frozen] on concrete classes to get the correct reference. Any idea why that is or if it's possible to have it freeze for concrete classes too?Seringapatam
IIRC, none of the Auto-mocking extensions mock concrete classes. This is by design. This post explains it in the context of AutoMoq, but AutoNSubstitute ought to be consistent with that behaviour too: blog.ploeh.dk/2010/08/25/…Christie
I have a concrete (Configuration) class defined in the test's parameters. The SUT (also inside parameter of the test) is being injected with the instance of Configuration specified. Placing [Frozen] around the Configuration parameter gives me the ability to modify it's behavior while inside the SUT.Seringapatam
Hey Mark, is there a way to make this handle delegates too? It seems that delegates don't get substituted and passed in.Seringapatam
IIRC, Moq doesn't do delegates, so the first step would be to find a Test Double library that does (I think Rhino Mocks do, but that's sort of defunct). The next step would be to write a filter that includes delegates when requested, but IIRC, AutoFixture already generates delegates for you.Christie
S
3

I ended up copying code from the AutoDataAttribute class and modifying it to include a FreezingCustomization.

This is the resulting attribute.

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);
    }
}
Seringapatam answered 6/1, 2014 at 21:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.