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()))
    {
    }
}
Josh Gallagher
  • 5,211
  • 2
  • 33
  • 60
Omar
  • 39,496
  • 45
  • 145
  • 213

2 Answers2

8

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()))
    {
    }
}
Omar
  • 39,496
  • 45
  • 145
  • 213
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 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()))));` – Omar Jan 07 '14 at 16:13
  • 2
    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? – Mark Seemann Jan 07 '14 at 16:25
  • It worked when I added it either `fixture.ResidueCollectors` or `fixture.Customizations`. Not sure why though. – Omar Jan 07 '14 at 16:57
  • 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? – Omar Jan 10 '14 at 17:03
  • 1
    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: http://blog.ploeh.dk/2010/08/25/ChangingthebehaviorofAutoFixtureauto-mockingwithMoq – Mark Seemann Jan 10 '14 at 21:59
  • 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. – Omar Jan 10 '14 at 22:20
  • Hey Mark, is there a way to make this handle delegates too? It seems that delegates don't get substituted and passed in. – Omar Jul 30 '14 at 15:39
  • 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. – Mark Seemann Jul 31 '14 at 05:16
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);
    }
}
Omar
  • 39,496
  • 45
  • 145
  • 213