Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 734
NightOwl888 Avatar asked Oct 09 '13 23:10

NightOwl888


3 Answers

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
like image 145
bbodenmiller Avatar answered Nov 11 '22 12:11

bbodenmiller


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).

like image 11
Pablo Romeo Avatar answered Nov 11 '22 11:11

Pablo Romeo


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;
}
like image 7
bricelam Avatar answered Nov 11 '22 12:11

bricelam