Easy Testing with AutoFixture and Dapper.Rainbow

When writing tests for code that interacts with SqlServer, there is usually a good bit of boiler plate to set up. The code mainly includes model instantiation and basic db CRUD operations for that model. With n number of models, setting up a test-suite can quickly become a drag. Luckily we can leverage AutoFixture, Dapper, and Dapper.Rainbow to factor out much of this boilerplate.

Let's say we're working with a db table named BattleMonkey, a corresponding BattleMonkey poco, and some BattleMonkey service. We want to place this service under test, namely the CalculateAllianceStability method, which calculates the stability modifier of any two BattleMonkeys.

int CalculateAllianceStability(BattleMonkey a, BottleMonkey b)

Enter AutoFixture

Starting with an NUnit test fixture we might have something that looks like this.

[TestFixture]
public class BattleMonkeyServiceFixture
{
	[test]
	public void CalculateAllianceStability()
	{
		var service = new BattleMonkeyService();

		var monkeyA = new BattleMonkey
		{
			Family = "test-family",
			Genus = "test-genus",
			Species = "test-species",
			BattleClan = "fire"
		}

		var monkeyB = new BattleMonkey
		{
			Family = "test-family",
			Genus = "test-genus",
			Species = "test-species",
			BattleClan = "wind"
		}

		Assert.That(service.CalculateAllianceStability(monkeyA, monkeyB),
		Is.EqualTo(5));
	}
}

Note that only the BattleClan property affects the service methods outcome, the rest is just test data. Let's let AutoFixture write this code for us.

> install-package autofixture

[TestFixture]
public class BattleMonkeyServiceFixture
{
	[test]
	public void CalculateAllianceStability()
	{
		var autoFixture = new Fixture();
		var service = new BattleMonkeyService();

		var monkeyA = autoFixture.Create<BattleMonkey>();
		monkeyA.BattleClan = "fire";

		var monkeyB = autoFixture.Create<BattleMonkey>();
		monkeyb.BattleClan = "wind";

		Assert.That(service.CalculateAllianceStability(monkeyA, monkeyB),
		Is.EqualTo(5));
	}
}

AutoFixture instantiates the object with random test data in each property. Leaving us to only worry about the essential test specific input data. This is the simplest use case for AutoFixture, however, it has many more features and various active extension projects.

Dapper and Dapper.Rainbow

If the service was well designed, we would be able to inject a mocked DAL with a lib like moq, such that we wouldn't have to directly interact with the database. However, let's assume that either we cannot inject the underlying DAL into the BattleMonkeyService, or that we are doing a higher level "integration test" where we need to test the system as a whole. In both of these cases we need to have test data in the database.

Generally, the best practice is for our test harness to create and clean up any test data it uses on each test run. We need a minimal DAL for each entity, with Dapper.Rainbow we can have this with just a few lines of code. One cool aspect about Dapper and Dapper.Rainbow is that you can include the code directly in your project as they are written as single file includes. Here we will install the Dapper core via Nuget and install the latest version of Dapper.Rainbow via a file include.

> install-package dapper

Pull down the contents of the Dapper.Rainbow DataBase.cs resource into your project. After rebuild, injecting our test data into the database now looks like this.

[TestFixture]
public class BattleMonkeyServiceFixture
{
	public class MyDatabase : Database<MyDatabase>
	{
		public Table<BattleMonkey> BattleMonkey { get; set; }
	}

	[test]
	public void CalculateAllianceStability()
	{
		var autoFixture = new Fixture();
		var service = new BattleMonkeyService();

		var monkeyA = autoFixture.Create<BattleMonkey>();
		monkeyA.BattleClan = "fire";

		var monkeyB = autoFixture.Create<BattleMonkey>();
		monkeyb.BattleClan = "wind";

		using(var conn = new SqlConnection(cstring))
		{
			conn.Open();
			var db = MyDatabase.Init(conn, commandTimeout: 2);
			db.BattleMonkey.Insert(monkeyA);
			db.BattleMonkey.Insert(monkeyB);
		}

		Assert.That(service.CalculateAllianceStability(monkeyA, monkeyB),
		Is.EqualTo(5));
	}
}

Which is a nice alternative to manually wiring up ADO or the maintenance overhead of EF. Cleanning up after ourseleves is just as easy.

[TestFixture]
public class BattleMonkeyServiceFixture
{
	public List<int> TestMonkeyIds = new List<int>();

	public class MyDatabase : Database<MyDatabase>
	{
		public Table<BattleMonkey> BattleMonkey { get; set; }
	}

	[TearDown]
	public void TearDown()
	{
		foreach(var id in TestMonkeyIds)
		{
			using (var conn = new SqlConnection(cstring))
			{
				conn.Open();
				var db = MyDatabase.Init(conn, commandTimeout: 2);
				db.BattleMonkey.Delete(id);
			}
		}
	}

	[test]
	public void CalculateAllianceStability()
	{
		var autoFixture = new Fixture();
		var service = new BattleMonkeyService();

		var monkeyA = autoFixture.Create<BattleMonkey>();
		monkeyA.BattleClan = "fire";

		var monkeyB = autoFixture.Create<BattleMonkey>();
		monkeyb.BattleClan = "wind";

		using(var conn = new SqlConnection(cstring))
		{
			conn.Open();
			var db = MyDatabase.Init(conn, commandTimeout: 2);
			var id = db.BattleMonkey.Insert(monkeyA).Value;
			TestMonkeyIds.Add(id);
			id = db.BattleMonkey.Insert(monkeyB).Value;
			TestMonkeyIds.Add(id);
		}

		Assert.That(service.CalculateAllianceStability(monkeyA, monkeyB),
		Is.EqualTo(5));
	}
}

Write More Test Code

While this example is trivial, the value increases as your test code grows. This reduces inital implementation investment as well as maintenance cost, ultimately increasing overall return on investment. Anything that motivates devs to consistently write more tests is just alright with me.