NightOwl888
NightOwl888

Reputation: 56909

Entity Framework Migrations - How to create a Unit Test to Ensure Migrations Model is up to Date?

I am using continuous integration with TeamCity, NUnit, and Git. I recently migrated (pardon the pun) from FluentMigrator to Entity Framework Migrations. I primarily did this to take advantage of the scaffolding functionality therein.

However, it is potentially possible to check in some changes to source control without having first scaffolded the changes into migrations (imagine the scenario where the application was not run before committing and pushing the commit). I am using a pre-tested commit workflow, so I would like to detect this problem in the pre-test rather than waiting until calling migrate.exe (which would be too late in my workflow and break the "green repository").

My question is, how to create a unit/integration test to detect when the migrations model doesn't match the context model so I can fail the build before attempting to migrate? Do note that I expect it not to match the database and would prefer not to access the database from the test.

I tried this approach:

    [Test]
    public void CheckWhetherEntityFrameworkMigrationsContextIsUpToDate()
    {
        Assert.DoesNotThrow(() =>
        {
            CallDatabase();
        });

    }

    private void CallDatabase()
    {
        using (var ctx = new MyContext("SERVER=(local);DATABASE=MyDatabase;Integrated Security=True;"))
        {
            var tenant = (from t in ctx.Tenant
                          select t).FirstOrDefault();
        }
    }

But this will always fail when there are pending migrations (rather than just in the case where the migrations model is not in sync with the context model, which is what I am after).

Update

I have added a work item on the EntityFramework project for this issue. Hopefully, they will look into adding a way to do this.

Upvotes: 14

Views: 11556

Answers (5)

ADM-IT
ADM-IT

Reputation: 4200

EF Core has a pretty straight forward method:

myDbContext.Database.Migrate();

Or you can check for pending migrations like that:

var migrator = myDbContext.GetInfrastructure().GetService<IMigrator>();

var migrations = myDbContext.Database.GetPendingMigrations();

foreach (var migration in migrations)
{
     migrator.Migrate(migration);
}

Upvotes: 1

bbodenmiller
bbodenmiller

Reputation: 3181

I wanted the best of all the answers given here so far so here's what I've come up with:

[TestClass()]
public class MigrationsTests
{
    [TestMethod()]
    public void MigrationsUpDownTest()
    {
        // Unit tests don't have a DataDirectory by default to store DB in
        AppDomain.CurrentDomain.SetData("DataDirectory", System.IO.Directory.GetCurrentDirectory());

        // Drop and recreate database
        BoxContext db = new BoxContext();
        db.Database.Delete();

        var configuration = new Migrations.Configuration();
        var migrator = new DbMigrator(configuration);

        // Retrieve migrations
        List<string> migrations = new List<string>;
        migrations.AddRange(migrator.GetLocalMigrations());

        try
        {
            for (int index = 0; index < migrations.Count; index++)
            {
                migrator.Update(migrations[index]);
                if (index > 0) {
                    migrator.Update(migrations[index - 1]);
                } else {
                    migrator.Update("0"); //special case to revert initial migration
                }
            }

            migrator.Update(migrations.Last());
        }
        catch (SqlException ex)
        {
            Assert.Fail("Should not have any errors when running migrations up and down: " + ex.Errors[0].Message.ToString());
        }

        // Optional: delete database
        db.Database.Delete();
    }

    [TestMethod()]
    public void PendingModelChangesTest()
    {
        // NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
        var migrationsConfiguration = new Migrations.Configuration();
        var migrator = new DbMigrator(migrationsConfiguration);
        var scriptingMigrator = new MigratorScriptingDecorator(migrator);

        try
        {
            // NOTE: Using InitialDatabase so history won't be read from the database
            scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
        }
        catch (AutomaticMigrationsDisabledException)
        {
            Assert.Fail("Should be no pending model changes/migrations should cover all model changes.");
        }
    }
}

Couple things worth noting:

  • You need to install Entity Framework in your test project
  • You need to add [assembly: InternalsVisibleTo("MyTestsProject")] to the top of your Configuration class
  • You need to specify an Entity Framework connection string in App.config that is only for test project since database will be deleted and recreated frequently - if running on build machine keep in mind that parallel runs could result in conflicts so you may want to change string per build

Upvotes: 5

Wouter Schut
Wouter Schut

Reputation: 926

I think this works better than Pablo Romeo's code. This upgrades and then downgrades one step again to catch missing items in the downgrade scripts.

[TestFixture]
class MigrationTest
{
    [Test]
    public void RunAll()
    {
        var configuration = new Configuration();
        var migrator = new DbMigrator(configuration);

        // Retrieve migrations
        List<string> migrations = new List<string> {"0"};  // Not sure if "0" is more zero than the first item in list of local migrations
        migrations.AddRange(migrator.GetLocalMigrations());

        migrator.Update(migrations.First());

        // Doe een stapje naar voren, en een stapje terug (www.youtube.com/watch?v=2sg1KAxuWKI)
        // (Dutch pun) meaning: take a small step forward, and a small step back ;)
        for (int index = 0; index < migrations.Count; index++)
        {
            migrator.Update(migrations[index]);
            if (index > 0)
                migrator.Update(migrations[index - 1]);
        }

        migrator.Update(migrations.Last());

        migrator.Update(migrations.First());
    }
}

Upvotes: 3

Pablo Romeo
Pablo Romeo

Reputation: 11406

In case anybody finds this useful, I test migrations by running all of them against a test database, using the following code:

[TestClass]
public class MigrationsTests
{
    [TestMethod]
    public void RunAll()
    {
        var configuration = new Configuration();
        var migrator = new DbMigrator(configuration);
        // back to 0
        migrator.Update("0");
        // up to current
        migrator.Update();
        // back to 0
        migrator.Update("0");
    }
}

This tests all Up and Down migrations, as well as detecting pending changes when automatic migrations is turned off.

NOTE: Make sure you run this against a test database. In my case the test project's app.config has the connectionString to the test database (just a local SQLExpress instance).

Upvotes: 12

bricelam
bricelam

Reputation: 30425

Here is some code that will check whether all model changes have been scaffolded to migrations or not.

bool HasPendingModelChanges()
{
    // NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
    var migrationsConfiguration = new Migrations.Configuration();
    var migrator = new DbMigrator(migrationsConfiguration);
    var scriptingMigrator = new MigratorScriptingDecorator(migrator);

    try
    {
        // NOTE: Using InitialDatabase so history won't be read from the database
        scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
    }
    catch (AutomaticMigrationsDisabledException)
    {
        return true;
    }

    return false;
}

Upvotes: 8

Related Questions