There's many well known benefits of testing your code, but for many developers testing is still the part of their job they like the least. They find it hard and painful, but it doesn't have to be that way.
Nowadays, there's great tooling to make it fun and productive and that's what this blog post is all about.

xUnit, AutoFixture and FakeItEasy are among my favorites framework/tool. They all achieve something very specific and they all play well together. I won't go over the details for each one, because there's a ton of documentation with great examples out there, but here's a quick look at what they do.

xUnit

xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages. xUnit.net works with ReSharper, CodeRush, TestDriven.NET and Xamarin.

AutoFixture

AutoFixture is an open source library for .NET designed to minimize the 'Arrange' phase of your unit tests in order to maximize maintainability. Its primary goal is to allow developers to focus on what is being tested rather than how to setup the test scenarios, by making it easier to create object graphs containing test data.

FakeItEasy

FakeItEasy is a .Net dynamic fake framework for creating all types of fake objects, mocks, stubs, etc.

In this post, what I'm more interested in, is to show you how to integrate them together. Let's do it with a hands-on example that we're gonna complete together.

First of all, install the following nuget packages in your test project.

  • xunit
  • AutoFixture
  • FakeItEasy
  • AutoFixture.Xunit2
  • AutoFixture.FakeItEasy

Class to test

Second of all let's define a simple example of a class to test a.k.a. System Under Test (sut)

public class StringService
{
    private INumberService _service;
    
    public StringService(INumberService service)
    {
        this._service = service;
    }
    
    public int ConvertToNumber(string value)
    {
        // Service that returns a number for each char in a string
        // corresponding to that letter position in the alphabet
        var numbers = this._service.Execute(value);
        return numbers.Sum();
    }
}

Test - V1

[Theory]
[InlineData(6, [1,2,3], "abc")]
[InlineData(15, [4,5,6], "def")]
public void GivenAString_WhenConvertToNumber_ThenSumOfLetterPositionIsReturned(int expected, 
    int[] charValues, string value)
{
    // Given
    var service = A.Fake<INumberService>();
    A.CallTo(() => service.Execute(A<string>._).Returns(charValues);
    var sut = new StringService(service);
    
    // When
    var result = sut.ConvertToNumber(value);
    
    // Then
    Assert.Equal(expected, result);
}

Pretty simple, it's neither too big nor dirty. We are leveraging [Theory] and [InlineData] to get a nice clean test, but it seems like we might miss some test cases.

Test - V2

Let's change the test to use [AutoData] in order to generate random test data and cover more edge cases

[Theory]
[AutoData]
[InlineAutoData([1,2,3], "abc")]
[InlineAutoData([4,5,6], "def")]
public void GivenAString_WhenConvertToNumber_ThenSumOfLetterPositionIsReturned(int[] charValues,
    string value)
{
    // Given
    var fixture = new Fixture();
    var service = A.Fake<INumberService>();
    A.CallTo(() => service.Execute(A<string>._).Returns(charValues);
    fixture.Register<INumberService>(()=> service);
    var sut = fixture.Create<StringService>();
    
    // When
    var result = sut.ConvertToNumber(value);
    
    // Then
    var expected = charValues.Sum();
    Assert.Equal(expected, result);
}

It's nice to have some auto generated test data, but the Given part of the test is getting quite big and harder to understand. We can probably do something with this.

Test - Final Version

Let's use everything in the toolbox, but to do so, we'll need to create our own integration between the frameworks.

Here's the actual implementation of the [AutoDataFakeItEasy] and [InlineAutoDataFakeItEasy] attributes that will make all of the heavy lifting for you.

Magic Attributes

[AttributeUsage(AttributeTargets.Method)]
public class AutoFakeItEasyDataAttribute : AutoDataAttribute
{
    public AutoFakeItEasyDataAttribute()
    : base(FixtureFactory)
    {
    }

    private static IFixture FixtureFactory()
    {
        return new Fixture().Customize(new AutoFakeItEasyCustomization());
    }
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class InlineAutoFakeItEasyDataAttribute : InlineAutoDataAttribute
{
    private readonly object[] _values;

    public InlineAutoFakeItEasyDataAttribute(params object[] values)
        : base(new AutoFakeItEasyDataAttribute(), values)
    {
        this._values = values;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        var data = base.GetData(testMethod).ToList();

        for (var i = 0; i < this._values.Length; i++)
        {
            data[0][i] = this._values[i];
        }

        return data;
    }
}

The final test

[Theory]
[AutoDataFakeItEasy]
[InlineAutoDataFakeItEasy([1,2,3])]
[InlineAutoDataFakeItEasy([4,5,6])]
public void GivenAString_WhenConvertToNumber_ThenSumOfLetterPositionIsReturned(int[] charValues, 
    string value, [Frozen]INumberService service, StringService sut)
{
    // Given
    A.CallTo(() => service.Execute(A<string>._).Returns(charValues);
    
    // When
    var result = sut.ConvertToNumber(value);
    
    // Then
    var expected = charValues.Sum();
    Assert.Equal(expected, result);
}

We still get all the benefits of auto generated test data and the flexibility to add our own specific test cases. The best part, we got rid of all the boiler plate code that was initializing our sut and its dependencies. The attributes will make sure to initialize our test values with inputs from the attribute or random data otherwise. They'll then make sure to instanciate mocks and dummy classes, also taking care of all their dependencies. This give us a very robust and maintainable test that will survive many refactors without needing any changes.

The only thing we need to define now is the behavior of the mock, calling the method under test and asserting the result. Wow, this is what every test should look like. Like they say: less is more.

Conclusion

This is a very simple sut and test. Imagine when you have a complex class to test, how much code and complexity you can save with these attributes.
As you can see, knowing the right tools can make you super efficient at writing good unit tests. Moreover, your tests will be simple to understand and maintain over time.