Isolating Integration Tests with EF4.x, Code First and MSTest

Wednesday, May 2, 2012 2:04 AM
Microsoft MVP Logo

Forward: this has nothing to do with SharePoint :)

I’m using Entity Framework 4.1+ and the cool Code First capability on a current project. For those unaware, Entity Framework (EF) is an code representation of your database. Code First (CF) is an approach where you define classes & relationships between them and let EF do the work of creating the DB and data model.

Spare me the debate about “I can write the schema & queries better than EF”… that’s great… golf clap for you. Sure could and so could I, but I also wouldn’t be nearly as productive in creating this app I’m working on. So why do I use it over doing it all by hand? Well it is (1) a level of abstraction which in my case makes me (2) more productive in creating the application and (3) is easy to maintain going forward not to mention (4) I love some of the capabilities baked into EF’s CF model, specifically DB initializers, seeding the database & the testability.

One very cool aspect to EF CS is it is very friendly to writing tests. I’m not a huge fan of unit tests & much prefer integration tests… in other words: I’d much rather see my code working against a real database with real data than mocking everything up. CF helps you with this. First, when you create your model via code, you can pass in these database initializers (see this blog post, jump to “Database Initializers”) . In a nutshell, you can take one of these classes and pass it to the EF CF engine and it will populate with YOUR seed data, great sample data!

So I was having a problem with these initializers & my tests. My challenge was that when my tests would run, they would seem to overlap with each other and some would fail. However when I ran the failing tests independently, they passed. Further inspection yielded that one test’s work in the DB was affecting what other tests were doing. The obvious solution to this was to utilize transactions (specifically the TransactionScope), but I was having trouble getting it to work as well as implementing it in my test projects. After a lot of searching I came up empty for an obvious answer but one response to a question did get me going in the right direction. Another challenge I had was that I wanted to use one set of DB initializers when for some tests (those that expected the DB to be empty) and another set for the DB to be seeded with sample data. I figured it would help if I posted the solution for others who search for the same stuff.

For reference, I use MSTest as my test framework, MOQ for mocking & the awesome Unit Test Runner tool window in Visual Studio that ships with DevExpress’ CodeRush.

The first thing I did was create a special class that would run once for all tests running under one test project. Creating a public static method decorated with the AssemblyInitialize attribute tells the MSTest framework to run this method once for each test run for the current assembly even if it includes multiple tests. I’m using this to force the creation of two databases: one that is seeded and one that is empty. After this runs I’ll have two databases that have the assembly in their name. Now my tests can be configured to use either database.

1: [TestClass]

2: public class TestRunDatabaseCreator {


4: public const string DATABASE_NAME_TEMPLATE = “CPT_CDS_{0}“;


6: [AssemblyInitialize]

7: public static void AssemblyInitialize(TestContext testContext) {

8: string projectName = GetCurrentProjectName();


10: Console.Out.WriteLine(“Test assembly init: creating unseeded DB…“);

11: CreateUnSeededDb(projectName);


13: Console.Out.WriteLine(“Test assembly init: creating seeded DB…“);

14: CreateSeededDb(projectName);

15: }


17: private static void CreateUnSeededDb(string projectName) {

18: Database.SetInitializer(new SmokeTestCreateDbWithNoDataIfNotExists());

19: Database.SetInitializer(new SmokeTestDropCreateDbWithNoDataAlways());


21: // create new DB connection to local SQL Server

22: string dbName = string.Format(DATABASE_NAME_TEMPLATE +”_UnSeeded”, projectName);

23: string connectionString = string.Format(TestGlobals.CONNECTION_STRING_TEMPLATE, dbName);

24: var dbfactory = new DatabaseFactory(connectionString);


26: // connect to DB to auto generate it

27: var customerContext = dbfactory.GetDataContext();

28: customerContext.Database.Initialize(true);

29: }


31: private static void CreateSeededDb(string projectName) {

32: Database.SetInitializer(new SmokeTestCreateDbWithDataIfNotExists());

33: Database.SetInitializer(new SmokeTestDropCreateDbWithDataAlways());


35: // create new DB connection to local SQL Server

36: string dbName = string.Format(DATABASE_NAME_TEMPLATE + “_Seeded”, projectName);

37: string connectionString = string.Format(TestGlobals.CONNECTION_STRING_TEMPLATE, dbName);

38: var dbfactory = new DatabaseFactory(connectionString);


40: // connect to DB to auto generate it

41: var customerContext = dbfactory.GetDataContext();

42: customerContext.Database.Initialize(true);

43: }

44: }

Next, I wanted all tests to use these databases & ensure that any changes each test made were isolated to just that specific test. The easiest way to do this was to use a TransactionScope object which I stuck in the TestInitialize attribute. As long as I didn’t complete/commit the transaction, the changes would never be committed to the DB. Since all the tests would use the same thing, I created a base test fixture which I could use in all tests as you see:

1: public class BaseTestFixture {

2: private const string CONNECTION_STRING_TEMPLATE = “server=localhost;database={0};uid=sa;pwd=Password1!;”;


4: private TransactionScope _transactionScope;


6: // DB factory from Entity Framework Code First

7: protected DatabaseFactory Dbfactory;

8: // Code First entity context

9: protected CustomerCacheContext CustomerContext;


11: public virtual string DatabaseName {

12: get { return string.Empty; }

13: }


15: [TestInitialize]

16: public void TestInitialize() {

17: // block init from firing

18: Database.SetInitializer(null);


20: // create new DB connection to local SQL Server

21: Dbfactory = new DatabaseFactory(GetConnectionString());


23: // connect to DB to auto generate it

24: CustomerContext = Dbfactory.GetDataContext();


26: // create transaction for everything in this testCreateStates

27: _transactionScope = new TransactionScope();

28: }


30: [TestCleanup]

31: public void Cleanup() {

32: if (_transactionScope != null) {

33: _transactionScope.Dispose();

34: _transactionScope = null;

35: }


37: CustomerContext.Dispose();

38: Dbfactory.Dispose();

39: }

40: }

Finally, I could create tests by inheriting the BaseTestFixture which not only created the EF CF context for me, but it also created the transaction scope for each test. There’s a virtual property (DatabaseName) which the test tells the base class which database to connect to, either the seeded or unseeded one.

1: [TestClass]

2: public class CustomerSmokeTests : BaseTestFixture {

3: public override string DatabaseName {

4: get {

5: string projectName = TestRunDatabaseCreator.GetCurrentProjectName();

6: string dbPrefixName = TestRunDatabaseCreator.DATABASE_NAME_TEMPLATE + “_Seeded”;


8: return string.Format(dbPrefixName, projectName);

9: }

10: }


12: [TestMethod]

14: public void TestGetCustomer() {

15: /// assign

16: CustomerContext.SaveChanges(); // trigger the initialization of DB

17: var customers = new CustomerRepository(CustomerContext);


19: /// act

20: // get customer

21: var customer = customers.GetOne(c => c.PrimaryEmail == “[email protected]”);


23: /// assert

24: Assert.IsNotNull(customer, “Failed to retrieve expected customer from DB.”);


26: // verify data for customer

27: Assert.IsNotNull(customer.CrmData, “Failed to get CRM data for customer.”);

28: }

29: }

Hope this little trick helps someone else out there. You might ask why I didn’t use ordered tests… I find those to be a pain to maintain and once I found that special AssemblyInitialize attribute, I felt like this was exactly what I needed.

comments powered by Disqus