Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Always Freeze mocks using AutoFixture, XUnit, and Moq

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()))
    {
    }
}
like image 550
Omar Avatar asked Jan 06 '14 17:01

Omar


1 Answers

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()))
    {
    }
}
like image 159
Mark Seemann Avatar answered Sep 20 '22 10:09

Mark Seemann