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).
I have added a work item on the EntityFramework project for this issue. Hopefully, they will look into adding a way to do this.
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:
[assembly: InternalsVisibleTo("MyTestsProject")]
to the top of your Configuration
classApp.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 buildIn 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).
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With