10

Due to the potential differences between Linq-to-Entities (EF4) and Linq-to-Objects, I need to use an actual database to make sure my query classes retrieve data from EF correctly. Sql CE 4 seems to be the perfect tool for this however I have run into a few hiccups. These tests are using MsTest.

The problem I have is if the database doesn't get recreated (due to model changes), data keeps getting added to the database after each test with nothing getting rid of the data. This can potentially cause conflicts in tests, with more data being returned by queries than intended.

My first idea was to initialize a TransactionScope in the TestInitialize method, and dispose the transaction in TestCleanup. Unfortunately, Sql CE4 does not support transactions.

My next idea was to delete the database in TestCleanup via a File.Delete() call. Unfortunately, this seems to not work after the first test is run, as the first test's TestCleanup seems to delete the database, but every test after the first does not seem to re-create the database, and thus it gives an error that the database file is not found.

I attempted to change TestInitialize and TestCleanup tags to ClassInitialize and ClassCleanup for my testing class, but that errored with a NullReferenceException due to the test running prior to ClassInitialize (or so it appears. ClassInitialize is in the base class so maybe that's causing it).

I have run out of ways to effectively use Sql CE4 for testing. Does anyone have any better ideas?


Edit: I ended up figuring out a solution. In my EF unit test base class I initiate a new instance of my data context and then call context.Database.Delete() and context.Database.Create(). The unit tests run a tad slower, but now I can unit test effectively using a real database


Final Edit: After some emails back and forth with Microsoft, it turns out that TransactionScopes are now allowed in SqlCE with the latest release of SqlCE. However, if you are using EF4 there are some limitations in that you must explicitly open the database connection prior to starting the transaction. The following code shows a sample on how to successfully use Sql CE for unit/functional testing:
    [TestMethod]
    public void My_SqlCeScenario ()
    {
        using (var context = new MySQLCeModelContext()) //ß derived from DbContext
        {
            ObjectContext objctx = ((IObjectContextAdapter)context).ObjectContext;
            objctx.Connection.Open(); //ß Open your connection explicitly
            using (TransactionScope tx = new TransactionScope())
            {

                var product = new Product() { Name = "Vegemite" };
                context.Products.Add(product);
                context.SaveChanges();
            }
            objctx.Connection.Close(); //ß close it when done!
        }
    }
KallDrexx
  • 27,229
  • 33
  • 143
  • 254
  • Of course SQL CE supports transactions... but using TransactionScope is a very wrong way of doing it. Just do it normally via the Connection object. – leppie Jan 13 '11 at 05:12
  • I am not sure how with EF4 entities without `TransactionScope`, unless you mean not calling `SaveChanges()`, which means the tests are not valid tests. – KallDrexx Jan 14 '11 at 13:43
  • Can you provide an example of how you seed the data with Sql CE? I use EF6 and would like to test it using sql ce –  Mar 27 '16 at 18:38

2 Answers2

4

In your TestInitialize you should do the following:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new System.Data.Entity.Database.DropCreateDatabaseAlways<YourEntityFrameworkClass>());

This will cause entity framework to always recreate the database whenever the test is run.

Incidentally you can create an alternative class that inherits from DropCreateDatabaseAlways. This will allow you to seed your database with set data each time.

public class DataContextInitializer : DropCreateDatabaseAlways<YourEntityFrameworkClass> {
    protected override void Seed(DataContext context) {
        context.Users.Add(new User() { Name = "Test User 1", Email = "test@test.com" });
        context.SaveChanges();
    }
}

Then in your Initialize you would change the call to:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new DataContextInitializer());
Buildstarted
  • 26,529
  • 10
  • 84
  • 95
  • After finally getting a chance to check this out, this doesn't seem to work. It seems to be dropping the database before running any tests, but it is not dropping the database before running each test individually. This is causing data to persist across unit tests, causing some other tests to fail. I can work around it for now, but I would rather it work without that. – KallDrexx Jan 14 '11 at 01:32
  • Nevermind - it's much too convoluted and doesn't lend well to unit testing...I basically use it to map multiple tables to a single entity and it's done in a new assembly each time i call it for that specific table...hard to explain really :) – Buildstarted Jan 14 '11 at 02:30
  • Couldn't you just clear the tables after each test and re-populate them with the seed method? Or is each test a different set of tables and what not? – Buildstarted Jan 14 '11 at 02:30
  • Some tests I am testing User queries, some I am testing Project queries, etc.. So each test is using different data, so I would have to write commands manually to clear each and every table, after every test, which seems like a lot of hard work and maintenance as my app grows, but maybe that is the only way (though that forces me to expose deleteAll methods in my repositories). As I start writing more aggregate based tests (e.g. search for a keyword in all values) I can see myself getting burned by persisted data between tests. – KallDrexx Jan 14 '11 at 13:20
  • Agreed. Normally, when I write unit tests for data i don't use the actual database but a mock database. But you're looking to specifically unit-test sqlce here? – Buildstarted Jan 14 '11 at 16:52
  • I'm more unit testing that any Linq queries I write are correctly translated to the correct SQL. So for example, if I am trying to retrieve all projects with a keyword, I need to make sure the linq composition works for EF4, since i have encountered circumstances where Linq statements work against in memory objects (e.g. `List`) but fail to translate into valid SQL. These situations make unit testing with a mock-database usless imho. – KallDrexx Jan 15 '11 at 19:46
  • 1
    I marked your post as the solution to give you rep for coming back and giving me some advice. From your advice I found a more reliable solution, which I put in the original post! Thanks! – KallDrexx Jan 16 '11 at 04:29
  • I'm late to the ball game, but ctx.Database.Initialize(true); together with above soltuion will force recreation of DB between tests – Anders Feb 17 '16 at 17:14
3

I found the approach in the "final edit" works for me as well. However, it's REALLY annoying. It's not just for testing, but any time you want to use TransactionScope with Entity Framework and SQL CE. I want to code once and have my app support both SQL Server and SQL CE, but anywhere I use transactions I have to do this. Surely the Entity Framework team should have handled this for us!

In the meantime, I took it one step farther to make it a little cleaner in my code. Add this block to your data context (whatever class you derive from DbContext):

public MyDataContext()
{
    this.Connection.Open();
}

protected override void Dispose(bool disposing)
{
    if (this.Connection.State == ConnectionState.Open)
        this.Connection.Close();

    base.Dispose(disposing);
}

private DbConnection Connection
{
    get
    {
        var objectContextAdapter = (IObjectContextAdapter) this;
        return objectContextAdapter.ObjectContext.Connection;
    }
}

This makes it a lot cleaner when you actually use it:

using (var db = new MyDataContext())
{
    using (var ts = new TransactionScope())
    {
        // whatever you need to do

        db.SaveChanges();
        ts.Complete();
    }
}

Although I suppose that if you design your app such that all changes are committed in a single call to SaveChanges(), then the implicit transaction would be good enough. For the testing scenario, we want to roll everything back instead of calling ts.Complete(), so it's certainly required there. I'm sure there are other scenarios where we need the transaction scope available. It's a shame it isn't supported directly by EF/SQLCE.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Interesting solution. I just did a workaround where my `IUnitOfWork` class has a `BeginTransaction()` and `EndTransaction(bool commit)` methods. `BeginTransaction` will open the connection if it doesn't exist yet, and `EndTransaction` will allow me to complete or rollback the transaction easily. So far this seems to work as well. Hopefully in the future though I could just use transaction scopes by themselves. – KallDrexx Mar 17 '11 at 13:13