I have covered some automated testing topics on this blog in the past, including mocking using Moq. Like this one, for instance. Today, let’s look at two related tools that will further assist you with your .NET automated testing needs – AutoFixture and AutoMoq.
What problems are they solving?
If you follow the AAA pattern for writing your tests – Arrange, Act, Assert – both AutoFixture and AutoMoq are tools that primarily aid in the Arrange section of your tests. Often, the arrange portion of the tests tend to be the most difficult, usually the lengthier section when compared to the other two sections and often the most tedious – setting up various properties in objects that you are not necessarily testing but still needs to be populated correctly for your code to execute. In short, you can use AutoFixture to automatically populate values into those based solely on data-types or with additional instructions you specify, and the tool will new up an instance of the object you need without you having to set everything up, manually.
Example of the Problem
Consider my Greeter
class below. It has a method named Greet
that takes a Person
object (also defined in the snippet below) as input. It logs the call invocation to a logger, inserts the greeting call to a database and then echoes out a greeting with the passed in person’s first name in the greeting.
public class Greeter { private readonly IDatabase database; private readonly ILogger<Greeter> logger; public Greeter(IDatabase database, ILogger<Greeter> logger) { this.database = database; this.logger = logger; } public async Task<string> Greet(Person person) { logger.LogInformation("Greeting {person}", person); await database.ExecuteFileAsync("insert-greeting.sql", new { first_name = person.FirstName, last_name = person.LastName, email_address = person.Email }); return $"Hello, {person.FirstName}"; } } public class Person { public Person(string firstName, string lastName, string email) { if (string.IsNullOrWhiteSpace(firstName)) throw new ArgumentNullException(nameof(firstName)); if (string.IsNullOrWhiteSpace(lastName)) throw new ArgumentNullException(nameof(lastName)); if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email)); this.FirstName = firstName; this.LastName = lastName; this.Email = email; } public readonly string FirstName; public readonly string LastName; public readonly string Email; }
How do you test that the Greet method echoes out the correct greeting output?
In order to write this simple test, you need to do quite a lot of extraneous setup work that is not directly related to the thing you are testing. Even in this simple, contrived example, you need to create a mock database service class, a mock logging class, create a Person object with a first name, last name and an email address (although the greeting simply uses the first name property of the Person class), all to simply invoke the Greet method and get a string output back which you can then write an assertion on. Something like the test, shown below (I have written the test using xUnit testing framework and Moq, a mocking library for .NET but the problem that I’m describing is not unique to these frameworks):
public class GreeterTests { [Fact] public async Task Greet_EchoesFirstName() { // Arrange var mockDatabase = new Mock<IDatabase>(); var mockLogger = new Mock<ILogger<Greeter>>(); var greeter = new Greeter(mockDatabase.Object , mockLogger.Object); Person person = new Person("Tom", "Vaidyan", "tom@test.com"); // Act var result = await greeter.Greet( person); // Assert Assert.True(result == $"Hello, {person.FirstName}"); } }
Add AutoFixture
Part of the problem shown above can be alleviated by using the AutoFixture library. Below is the same test, rewritten to use AutoFixture:
[Fact] public async Task Greet_EchoesFirstName_v2() { // Arrange var mockDatabase = new Mock<IDatabase>(); var mockLogger = new Mock<ILogger<Greeter>>(); var greeter = new Greeter(mockDatabase.Object , mockLogger.Object); // Use AutoFixture to create a Person var fixture = new Fixture(); var person = fixture.Create<Person>(); // Act var result = await greeter.Greet( person); // Assert Assert.True(result == $"Hello, {person.FirstName}"); }
In the example above, we alleviated part of the problem by using AutoFixture to create a Person object automatically to use in our test. This relieved us from the job of creating a person object manually which required us to specify a first name, last name and an email address. The library on the other hand, using some reflection magic, figured out what parameters are needed for creating a Person object and new’d one up accordingly. If you debug the test shown above, and inspect the Person object it created, you’ll see that it is generating GUIDs and populating the string parameters with those.
If needed, the library is also flexible enough to allow us to inform it on how it should create objects.
Add AutoMoq
But we can do better. In the above example, I still had to create a mock Database service object and a mock Logger object. If we tell AutoFixture to do it, it will complain that both IDatabase and ILogger are interfaces and that we must point AutoFixture to concrete implementations of those for AutoFixture to create objects of those types. Although that’s a viable option, I added a second library to the mix – AutoMoq – to automatically derive this by looking at the shape of the Interface. Look at the same test, now with AutoMoq added:
[Fact] public async Task Greet_EchoesFirstName_v3() { // Arrange var fixture = new Fixture(); fixture.Customize(new AutoMoqCustomization()); var greeter = fixture.Create<Greeter>(); var person = fixture.Create<Person>(); // Act var result = await greeter.Greet( person); // Assert Assert.True(result == $"Hello, {person.FirstName}"); }
With the AutoMoqCustomization that we added above, when AutoFixture now encounters our request to create a new Greeter instance, it can create it, even when Greeter depends on IDatabase and ILogger, both entities which AutoMoq will now auto-generate for AutoFixture. While the version above is much cleaner than what we originally started with, we can do a few more improvements. For instance, it would get tedious to specify the AutoMoqCustomization in each and every test that you write. You can offload that work into an attribute definition and then simply add that decorator to the tests where you want to utilize them:
public class AutoMoqDataAttribute : AutoDataAttribute { public AutoMoqDataAttribute() : base(() => new Fixture().Customize(new AutoMoqCustomization())) { } }
If you couple the attribute above with the [Theory]
attribute provided in xUnit that allows for passing data into your tests in a declarative fashion, your tests can become even more succinct.
[Theory, AutoMoqData] public async Task Greet_EchoesFirstName_v4(Greeter greeter, Person person) { var result = await greeter.Greet(person); Assert.True(result == $"Hello, {person.FirstName}"); }
Closing Remarks
In the final version of our test, we were able to completely eliminate the Assert portion of our test by reducing it down to an annotation and a couple of input params that we simply had to specify and then AutoFixture and AutoMoq libraries took the whole responsibility of generating the objects we needed for testing. These tools can certainly help clean up your tests but moreover, they also have the good side-effect of making your tests a bit more robust. For instance, consider a scenario where the Greeter
class from the above examples were undergoing a modification where a new third interface – say, an IEmailer
was now being injected in through the constructor. If you were manually new’ing up the Greeter class, all your tests would now fail even though none of those tests are depended on that new addition! If on the other hand, you are using AutoFixture and AutoMoq, the tools would see the new interface that is now expected for the object creation and automatically create it accordingly without any of your existing tests failing.