AutoData Theories with AutoFixture using manual fakes
Asked Answered
P

3

7

Given this system to test:

public class MySut
{
    private readonly IHardToMockDependency _hardToMockDependency;

    public MySut(IHardToMockDependency hardToMockDependency,
                 IOtherDependency otherDependency)
    {
        _hardToMockDependency = hardToMockDependency;
    }

    public string GetResult()
    {
        return _hardToMockDependency.GetResult();
    }
}

public interface IOtherDependency { }

public interface IHardToMockDependency
{
    string GetResult();
}

And this unit test:

internal class FakeHardToMockDependency : IHardToMockDependency
{
    private readonly string _result;

    public FakeHardToMockDependency(string result)
    {
        _result = result;
    }

    public string GetResult()
    {
        return _result;
    }
}

public class MyTests
{
    [Fact]
    public void GetResultReturnsExpected()
    {
        string expectedResult = "what I want";
        var otherDependencyDummy = new Mock<IOtherDependency>();
        var sut = new MySut(new FakeHardToMockDependency(expectedResult),
                            otherDependencyDummy.Object);

        var actualResult = sut.GetResult();

        Assert.Equal(expectedResult, actualResult);
    }
}

How should I convert it to use AutoFixture.Xunit and AutoFixture.AutoMoq (while still using the manual fake)?

In real-world tests, the manual fake would have a more complex interface and behavior. Note that I want to pass the anonymous variable (the expectedResult string) to the constructor of the manual fake.

Poss answered 15/3, 2013 at 21:48 Comment(0)
P
10

There are already some good answers here, but I'd like to suggest a simpler alternative that involves slightly loosening the invariants of the FakeHardToMockDependency class. Make it public, and provide a way to assign the result that is decoupled from the constructor:

public class FakeHardToMockDependency : IHardToMockDependency
{
    private string _result;

    public FakeHardToMockDependency(string result)
    {
        _result = result;
    }

    internal string Result
    {
        get { return _result; }
        set { _result = value; }
    }

    public string GetResult()
    {
        return _result;
    }
}

Notice that I've added an internal property and removed the readonly keyword from the field.

This enables you to refactor the original test to this:

[Theory, AutoMoqData]
public void GetResultReturnsExpected_AutoDataVersion(
    [Frozen(As = typeof(IHardToMockDependency))]FakeHardToMockDependency fake,
    MySut sut)
{
    var expected = "what I want";
    fake.Result = expected;

    var actual = sut.GetResult();

    Assert.Equal(expected, actual);
}

For completeness, here's the AutoMoqDataAttribute code:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }
}
Put answered 16/3, 2013 at 8:18 Comment(5)
Now there you go and take the golden hammer out of my hand. ;-) But what difference does the internal make here, as opposed to public? The SUT can't see the property either way, because it only gets the interface.Cadastre
@GCATNM It doesn't really matter whether the Result property is internal or public. Personally, I'd make it public, but the OP provided the fake as an internal class, so I just wanted to change as little as possible to make the suggested modifications more acceptable :)Put
Wow, three great answers, and one from the author himself! Loosening the invariants of the fake is fine. Thank you! Accepting just one answer is going to be tough - I'll have to try these.Poss
This is what I needed. I didn't know about the As option of Frozen. The other solutions could be very useful for other scenarios (and @GCATNM's explanation is excellent), but here the anonymous string variable is auto-mocked and injected into the fake by default, which is what I want. I don't need to expose a setter on fake.Result, since AutoFixture already provided a value to the fake's constructor. In this simple example, my Assert could be Assert.Equal(fake.GetResult(), actualResult).Poss
The As property is a rather elusive beast indeed. I only discovered this when I needed it and started looking into how it would be possible to implement this. Well, foolish was I to think I should be able to improve AutoFixture. ;-)Cadastre
C
4

Depending on what kind of parameters you need to pass to your manual fake, you might be able to use a parameterized attribute, similar to the AutoFixture's built-in InlineAutoDataAttribute.

Given these

public interface IHardToMockDependency
{
    string Value { get; }
}

public class FakeHardToMockDependency : IHardToMockDependency
{
    private readonly string _value;

    public FakeHardToMockDependency(string value)
    {
        _value = value;
    }

    #region IHardToMockDependency Members

    public string Value
    {
        get { return this._value; }
    }

    #endregion IHardToMockDependency Members
}

you create an ICustomization implementation that tells a fixture object how to create an implemention of the IHardToFakeDependency interface:

public class FakeHardToMockDependencyCustomization : ICustomization
{
    private readonly string _value;

    public FakeHardToMockDependencyCustomization(string value)
    {
        _value = value;
    }

    #region ICustomization Members

    public void Customize(IFixture fixture)
    {
        fixture.Register<IHardToMockDependency>(() => new FakeHardToMockDependency(this._value));
    }

    #endregion ICustomization Members
}

Note that this needs to know the string you want to pass in, of course.

Next, you roll up this with the other customizations you want to use in a CompositeCustomization:

public class ManualFakeTestConventions : CompositeCustomization
{
    public ManualFakeTestConventions(string value)
        : base(new FakeHardToMockDependencyCustomization(value), new AutoMoqCustomization())
    {
    }
}

Make sure you always put the customizations in order from most specific to most general, as explained here by Mark Seemann.

Now you create an AutoDataAttribute implementation that uses this customization:

public class ManualFakeAutoDataAttribute : AutoDataAttribute
{
    public ManualFakeAutoDataAttribute(string value)
        : base(new Fixture().Customize(new ManualFakeTestConventions(value)))
    {
    }
}

This can now be used in the same way as an InlineAutoDataAttribute:

public class ManualFakeTests
{
    [Theory, ManualFakeAutoData("iksdee")]
    public void ManualFake(IHardToMockDependency fake)
    {
        Assert.IsType<FakeHardToMockDependency>(fake);
        Assert.Equal("iksdee", fake.Value);
    }
}

You can also have it injected into an auto-created SUT instance right away by applying the [Frozen] attribute to the Theory parameter:

    [Theory, ManualFakeAutoData("iksdee")]
    public void SutWithManualFake([Frozen] IHardToMockDependency fake, MySut sut)
    {

    }

This will create a MySut instance and the IHardToMockDependency instance needed for the constructor, for which you have given AutoFixture a rule in the FakeHardToMockDependencyCustomization, and also give you that very instance as the fake variable.

Note that not freezing the fake would still give you a correct FakeHardToMockDependency instance as well as inject one into the sut, but those would be distinct, as we have registered a factory delegate in the customization. Freezing the instance will cause the fixture to always return the same instance for subsequent requests for the interface.

This has a few caveats, however:

  • You have no reference to the string you pass in as a parameter, so you have to have it in there as a string literal twice. You can work around this with string constants within the test class, for example.
  • The number of types that can be used as attribute parameters in .NET is limited. As long as you only need basic types, you should be fine, but calling constructors or the like in the parameter list is not possible.
  • You should only use this attribute when you need an instance if IHardToFakeDependency; otherwise you'll always have to pass in a string parameter anyway. If you have a set of standard customizations you need to use, create another attribute that contains only those.
  • If you need the capabilities of the InlineAutoDataAttribute at the same time, you also need to create yet another attribute that combines the features of both.

Depending on the concrete circumstances, you might also want to look at xUnit.net's PropertyDataAttribute, but I hardly ever find myself using that.

In general, in my opinion, understanding how to work with customizations and autodata attributes and when and how to create your own are the key to effectively using AutoFixture and really make it save you work.

If you often write code in a specific domain that you need to test, it might well make sense to create a library containing customizations, attribute and stub objects that will always be ready to use once you drop it in next to xUnit.net, AutoFixture and Moq. I know I'm damn glad I built mine.

Oh, and also: Having a dependency that is hard to mock might point at a design issue. Why is it that hard to mock?

Cadastre answered 16/3, 2013 at 1:43 Comment(1)
Thank you; very helpful! And I agree that this is a code smell that indicates a potential design issue.Poss
G
3

Maybe this is not the most idiomatic Autofixture setup but definitely works:

[Fact]
public void GetResultReturnsExpected()
{
    var fixture = new Fixture()
        .Customize(new AutoMoqCustomization());

    var expectedResult = fixture.Create<string>();

    fixture.Register<IHardToMockDependency>(
        () => new FakeHardToMockDependency(expectedResult));

    var sut = fixture.Create<MySut>();

    var actualResult = sut.GetResult();

    Assert.Equal(expectedResult, actualResult);
}

If you want to use also AutoData you can create your own AutoMoqData based on this great article where you can hide some or all of the fixture custimizations.

Something like:

public class MySutAutoDataAttribute : AutoDataAttribute
{
    public MySutAutoData()
        : base(new Fixture()
            .Customize(new AutoMoqCustomization()))
    {
        Fixture.Freeze<string>();

        Fixture.Register<IHardToMockDependency>(
            () => new FakeHardToMockDependency(Fixture.Create<string>()));
    }
}

And you can use it like:

[Theory, MySutAutoData]
public void GetResultReturnsExpected(MySut sut, string expectedResult)
{
    var actualResult = sut.GetResult();

    Assert.Equal(expectedResult, actualResult);
}

But you should note that there is room for a lots of improvement in the MySutAutoDataAttribute for example: it is not very generic and Fixture.Freeze<string>(); can cause problems if you are using multiple strings in your tests.

Giselle answered 15/3, 2013 at 22:31 Comment(3)
I realize I could do that, but my question is specifically about doing this with a descendant of AutoDataAttribute. Could you provide an example of doing that for this case?Poss
@Poss sorry I've missed your title that you want a solution with the AutoDataAttribute. I've updated my answer with a possible implementation.Giselle
Freezing the string like that gives you no control over it, however.Cadastre

© 2022 - 2024 — McMap. All rights reserved.