Entity Framework code first includes an interesting feature called database initializers. When I first encountered this feature I wondered if it would be possible to use a database initializer to drop and recreate a database as part of a suite of integration tests. It might prove very useful if we were able to create a set of repeatable and atomic tests around, for example, a data access layer. Of course it turns out that this is possible.
At the time of writing Entity Framework is in version 6.1.1.
What is a database initializer?
A database initializer is an implementation of the IDatabaseInitializer<TContext> interface and is used by Entity Framework to setup the database when the context is used for the first time. This could involve dropping and recreating the entire database, or just updating the schema if the model has changed. MSDN describes the interface as follows:
“An implementation of this interface is used to initialize the underlying database when an instance of a DbContext derived class is used for the first time. This initialization can conditionally create the database and/or seed it with data.” [1]
There are several implementations available out of the box:
- DropCreateDatabaseIfModelChanges<TContext> – will DELETE, recreate, and optionally re-seed the database only if the model has changed since the database was created.
- DropCreateDatabaseAlways<TContext> - will always recreate and optionally re-seed the database the first time that a context is used in the app domain. To seed the database, create a derived class and override the Seed method.
- CreateDatabaseIfNotExists<TContext> - will recreate and optionally re-seed the database only if the database does not exist. To seed the database, create a derived class and override the Seed method.
In our scenario we always want to reinitialize the database before each test. This will give us a repeatable baseline at the start of each test case. Also, we probably want to be able to insert some known test data. The DropCreateDatabaseAlways<TContext> class looks like it does exactly what we want: it always recreates the schema and can optionally re-seed the database.
Creating an initializer derived from DropCreateDatabaseAlways<TContext>
Creating our custom initializer turns out to be very simple:
namespace Andy.French.Repository.Entity.Framework.Tests { using System.Data.Entity; using System.Data.Entity.Migrations; using Andy.French.Repository.Entity.Framework.Tests.Domain; public class TestInitializer : DropCreateDatabaseAlways<TestContext> { protected override void Seed(TestContext context) { context.Customers.AddOrUpdate( c => c.Id, new Customer { Name = "Customer 1" }, new Customer { Name = "Customer 2" }); base.Seed(context); } } }
In this example I’m seeding the database with a couple of customers. Naturally you would seed the database according to your needs using your domain classes.
Invoking the initializer in our test suite
I use NUnit for tests - and have done so for a very long time – so the example below is based on NUnit.
For true atomic repeatable tests you should drop and recreate the database before each and every test case but for this example I have chosen to do so once for each test fixture - a class that contains tests. To do that I have created a class that’s marked with the SetUpFixture attribute. NUnit will pick this up and will run the method marked with the SetUp attribute for each test fixture and before any of the tests it contains are run.
namespace Andy.French.Repository.Entity.Framework.Tests { using System.Data.Entity; using NUnit.Framework; [SetUpFixture] public class SetUpFixture { public SetUpFixture() { } [SetUp] public void SetUp() { Database.SetInitializer(new TestInitializer()); var context = new TestContext(); context.Database.Initialize(true); } } }
That’s all here is to it! On line 16 we call the Database.SetInitializer method so Entity Framework will use our custom initializer. Remember, the custom initializer extends DropCreateDatabaseAlways<TestContext> so the initializer is set for the test context type.
On lines 18 and 19 we simply create a context and call context.Database.Initialize(true) which causes the database to be dropped, recreated and re-seeded. MSDN describes the Initialize method in the following terms:
“Runs the the registered IDatabaseInitializer<TContext> on this context. If "force" is set to true, then the initializer is run regardless of whether or not it has been run before. This can be useful if a database is deleted while an app is running and needs to be reinitialized. If "force" is set to false, then the initializer is only run if it has not already been run for this context, model, and connection in this app domain. This method is typically used when it is necessary to ensure that the database has been created and seeded before starting some operation where doing so lazily will cause issues, such as when the operation is part of a transaction.” [2]
Running a test
With everything in place we can now run some tests. Here’s an example where we are testing a customer repository:
namespace Andy.French.Repository.Entity.Framework.Tests { using System.Linq; using NUnit.Framework; [TestFixture] public class CustomerRepositoryTest { private CustomerRepository _repository; [SetUp] public void SetUp() { var context = new TestContext(); _repository = new CustomerRepository(context); } [Test] public void FindAll_WhenCalled_FindsAllInstances() { // Arrange // Act var result = _repository.FindAll(); // Assert Assert.That(result.Count(), Is.EqualTo(2)); } } }
A note on configuration
You will need to add an app.config file to your project. The config file will have to tell Entity Framework which database to use. It might look something like this:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/> </configSections> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0"/> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/> </providers> </entityFramework> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/> </startup> </configuration>
Note that we are using a SQL Server Express local database (v11.0). In my case the result is I get a couple of database files (.mdf and .ldf) named after the context used by the database initializer in my user directory.
References
[1] DatabaseInitializer<TContext> Interface (MSDN)
[2] Database.Initialize Method (MSDN)