Entity Framework & Database Testing Setup


When you're working on an application which uses some sort of database, there is always a point when you need to test some functionality directly on the database server - e.g. complex LINQ queries, transactions handling etc. In this post, we will setup the testing project for an app, where Entity Framework and code-first approach are used. Our target is simple - when tests runner is triggered, a testing db will be created, migrations will be applied and after all the tests will be executed, the db will be deleted.

Demo project overview

For this post, I've created a simple demo which keeps track of employees and their business trips. The solution contains only Dal project and is available on github - ef-codefirst-database-testing.
The model is simple - there are Employee and BusinessTrip entities with many-to-many relationship. DAL project implements EmployeesService and BusinessTripsService classes, which provide database related logic on the top of the database model. It uses Entity Framework, so it implements DatabaseContext class for communication with DB.

Testing project setup

For testing, we add another project in the demo solution and call it Dal.Tests. It references the DAL project and uses xUnit testing framework. We want to test the functionality implemented in the EmployeesService.cs and BusinessTripsService.cs classes, and we want to test it on a testing database, but with the same setup as in production.
So we create EmployeesServiceTests.cs and BusinessTripsServiceTests.cs classes with appropriate testing methods. Now we need to find a way how to setup the db before any of the tests will run, execute the tests on the testing db, and delete the db after testing.

Creating testing database

It's really simple to create a properly initialized testing database from scratch. Everything we need to do are the following steps.

Before I wrote this post, I used different and more complicated method for initialization of testing database. It involved sql commands for database creation and custom migrator class responsible for running migrations from Dal project. During the demo project implementation I discovered much simpler way, which I am gonna show you now.


1. Add connection string in the test project'sApp.config
  <connectionStrings>
    <add name="EmployeeTrips" connectionString="data source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\EmployeeTripsTestDb.mdf;initial catalog=EmployeeTripsTestDb;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
  </connectionStrings>
The connection string's name should be the same as defined in DatabaseContext constructor. We want to use (LocalDb)\MSSQLLocalDB as data source - that way, we don't need a full instance of SQL Server Express running on the testing environments. AttachDbFilename setting specifies the path and name of the database file and finally, the initial catalog setting specifies the name of the testing database in the SQL server instance catalog.
Ensure that you use different initial catalog as in the production environment, otherwise you can override the production database.
2. Create database from code
The trick for creating the testing database from scratch with the same model as defined in the Dal project lies in using the same database context in the testing project as in the Dal project. All you need to do is call the following piece of code.
AppDomain.CurrentDomain.SetData("DataDirectory", AppDomain.CurrentDomain.BaseDirectory);

using (var context = new DatabaseContext())
{
    if (context.Database.Exists())
    {
        context.Database.Delete();
    }
    context.Database.Create();
}
The context.Database.Create() method creates the db and run the migrations from the Dal project. By default, the database is created in the C:\Users\%username% folder. This could be a problem when the tests are running on a CI server (access rights, folder existence...). For that reason, with AppDomain.CurrentDomain.SetData we setup the DataDirectory to current AppDomain and the database will be created in the bin folder.

Using testing database fixture

Now we know how to create the testing database. However, where should we create it? When we put the code into the BusinessTripsServiceTests class constructor, the database will be created for every test method from this class separately (xUnit creates new instance of class for every test). This will terribly slow down the tests execution and because the tests run in parallel by default, it can also cause tests crashing.
The xUnit framework provides a functionality called fixtures, which allows to use the same test context across multiple tests. For the methods in the same class, we can implement Class Fixture. That way, the database will be created only once per all tests in the class, which is better, but not exactly what we want.
What we need to use is a Collection Fixture. That way, we can create a single test context with one testing db and share it among tests in several test classes, and have it cleaned up after all the tests in the test classes have finished.

The TestDbFixture implementation should look like this.

namespace Dal.Tests.Setup
{
    public class TestDbFixture : IDisposable
    {
        public TestDbFixture()
        {
            AppDomain.CurrentDomain.SetData("DataDirectory", AppDomain.CurrentDomain.BaseDirectory);

            using (var context = new DatabaseContext())
            {
                if (context.Database.Exists())
                {
                    context.Database.Delete();
                }

                context.Database.Create();
            }
        }

        public void Dispose()
        {
            using (var context = new DatabaseContext())
            {
                context.Database.Delete();
            }
        }
    }
}
Then create the collection definition class with proper [CollectionDefinition] attribute, which will identify the test collection.
namespace Dal.Tests.Setup
{
    [CollectionDefinition("UseDb")]
    public class TestDbCollection : ICollectionFixture<TestDbFixture>
    {
    }
}
And add the [Collection] attribute to all the test classes that will use the testing database.
namespace Dal.Tests
{
    [Collection("UseDb")]
    public class BusinessTripsServiceTests : IDisposable
    {
      .
      .
namespace Dal.Tests
{
    [Collection("UseDb")]
    public class EmployeesServiceTests : IDisposable
    {
      .
      .

You can check the full implementation in my demo project ef-codefirst-database-testing.

This way, all the tests will use the same testing database. It will be created before execution of the first test from the collection, and it will be deleted after all the tests in the collection have finished. And that's exactly the thing we wanted to achieve.

Viktor Borza

Freelance .NET developer with passion for software architecture and cutting-edge trends in programming.


Comments


Latest Posts

Browse

Archive All posts