2

After reading several questions/answers about unit testing Entity Framework, I have decided to forgo unit testing for integration testing. I am going in with the philosophy that interacting with the EF context is a "private" action, because it does not need to be unit tested independently of my service, and can't easily & accurately be mocked.

Note: In my example, I'm using EF5.

First, I have a service method for creating a user:

void CreateUser(string username, string password);

My test assembly has a SetUpFixture (one-time for test run) that creates of my database (EF Code First) and test data:

[SetUpFixture]
public class SetUpFixture
{
    [SetUp]
    public void SetUp()
    {
        using (var context = new MyDbContext())
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());

            // Set up a bunch of initial data and commit
        }
    }
}

Then before each test, my TestFixtureSetup method runs that creates an instance of my DB context, which is set to rollback when it's disposed (after each test), and also creates the instance of my service:

[TestFixtureSetUp]
public virtual void TestFixtureSetUp()
{
    _context = new MyContext(rollbackOnDispose: true);
    UserService = new SignupService(_context);
}

[TestFixtureTearDown]
public virtual void TestFixtureTearDown()
{
    Context.Dispose();
}

Finally, my actual integration test to make sure that what valid data is passed in, a record with my username got created (and here's where my problem lies):

[Test]
public void ValidDataShouldResultInNewRecordWithUsername()
{
    SignupService.CreateUser("myuser", "fakepassword");

    var q = from user in Context.Users 
            where user.Username == "myuser"
            select user;

    var actualUser = q.Single();

    Assert.AreEqual("myuser", actualUser.Username);
}

Here are my questions:

1) First of all, is this even the way to go testing services dependent on EF? I know there are several approaches, I just want to make sure there's nothing crazy with this approach.

2) Second, how should I verify the service method (CreateUser) did what it was supposed to do, before the data is committed (which I don't want it to commit, so that my database state remains as it was initialized prior to each test)? The query in the test above returns no data, since it wasn't committed yet.

Jerad Rose
  • 15,235
  • 18
  • 82
  • 153
  • 2
    Verify the expected behavior at the integration interface. Assuming you are confident with EF's behavior, you can always mock EF in your repository object, thereby alleviating the necessity of rolling back your data changes. Failing that, just do your data setup again before each test. – Robert Harvey Jul 08 '12 at 20:33
  • 1
    I'm not using a repository -- my services tie directly into EF. I know many oppose this design, but I've ready many posts from @LadislavMrnka that give very valid reasons why you should avoid repositories and true unit testing with EF. – Jerad Rose Jul 08 '12 at 20:35
  • Who? You certainly don't have to use a repository (changing the underlying data store is not a common occurrence), but I can't think of any evil reason not to, and a repository does give you some nice organizational capabilities. – Robert Harvey Jul 08 '12 at 20:36
  • See [this answer](http://stackoverflow.com/a/6904479/213902) for his justification and several links discussing this. – Jerad Rose Jul 08 '12 at 20:38
  • Well, then just set up your test data again before each test. Should be simple enough. – Robert Harvey Jul 08 '12 at 20:43
  • Simple, yes. But also extremely slow. EF database construction from code first takes several second. Magnify that by thousands of unit tests, and this becomes very impractical unfortunately. – Jerad Rose Jul 08 '12 at 20:45
  • No, no. Don't set up the entire database, sheesh. Just restore the data in the tables that are altered by the test. – Robert Harvey Jul 08 '12 at 20:45
  • I don't think that's quite as simple, at least not vs. Database.SetInitialize(). I'll look into it though. Still curious if there are other alternatives. – Jerad Rose Jul 08 '12 at 20:49

2 Answers2

6

For #2, if you are using SQL Server, you can use Snapshots. You create a snapshot when your test data has been loaded. Then, you run your test, and in the teardown (or whatever post test method you use), you revert to the snapshot. Reverting to a snapshot is very fast, so it is a practical way to test the DB.

There is another approach which I have used to some success. Instead of using a SQL database, use a SQLCE database. Then you can manage your test data as files - you are creating the database anyway. I like the snapshot approach better, but they both work.

For #1, this is an integration test. Hitting the database is exactly the right thing to do, as you are testing the data portion of the application. Abstracting out a repository buys you nothing but more complexity, as then you need to test the repository against the database.

Good luck, Erick

Erick T
  • 7,009
  • 9
  • 50
  • 85
  • Thanks, Erick. You're answer led me to find [this article](http://graemehill.ca/unit-testing-an-entity-framework-data-access-layer-part-2-rolling-back-the-test-database), which says the same thing you do. However, this blogger later posted [this article](http://graemehill.ca/high-performance-database-rollback-in-automated-tests-with-sql-server), which is what I ended up doing (see KallDrexx's answer below). I ultimately accepted his answer, but you still get a +1 for originally leading me to it. Thanks again! – Jerad Rose Jul 09 '12 at 03:21
  • That is a good approach, but I want to warn you that if your tests include any error conditions, you might end up with tests that pass that shouldn't and the other way around. – Erick T Jul 09 '12 at 04:54
  • I'm not sure I follow. What do you mean if my tests include error conditions? And why would this approach risk false positives? – Jerad Rose Jul 09 '12 at 04:57
  • Are sql server snapshots fast enough to revert in between each individual test? Thanks for the link, I've never knew about those before. – KallDrexx Jul 09 '12 at 12:48
  • Fast enough is a hard question. :) I found them fast enough to run approx 100 tests in a couple of minutes. The other nice thing is that even if you completely changed the data, the revert is still equally fast. It can be thought of as a pointer change. – Erick T Jul 09 '12 at 21:56
  • @Erick Do not use SQLCE for integration testing when you later switch to the Sql Server in production. This can have different results. – Elisabeth Feb 22 '13 at 21:04
2

This is close to how all my automated tests are (I don't unit test either, only integration test against a real db). There are two main things I do differently.

The first is I setup a transactionscope in my setup and dispose it in my tear down, without committing. This allows me to query for updated data w But be able to revert the database for the next test. The downside is that you probably can't use transactionscopes in your service layer as I don't know how well nested transactions work.

The other thing I do is trigger a full database rebuild at startup by calling the EF initialize class (I don't have my code in front of me ATM). This way I'm garanteed to have a fresh database from seed data at the beginning of the test run, and it also let's me know if my entity structure is bad,

KallDrexx
  • 27,229
  • 33
  • 143
  • 254
  • Funny, about 10 minutes ago I came across [this article](http://graemehill.ca/high-performance-database-rollback-in-automated-tests-with-sql-server) that says the same thing. I actually already had my SetUp doing the drop/recreate of the DB and setting up my fixtures. So all I needed to do was wrap my tests with a TransactionScope. I did that, now I'm all set. Regarding services, I don't plan on using MSDTC outside of my tests. I actually found [this article](http://blingcode.blogspot.com/2009/09/autofacs-extremely-powerful-and.html) talking about IoC and nested UoW, and may try that. – Jerad Rose Jul 09 '12 at 03:18
  • As I mentioned in a comment below, if your tests cover errors, you can easily mask failures when you use transactions in a test. – Erick T Jul 09 '12 at 04:56
  • In that comment below you don't elaborate on what errors transactions will hide. – KallDrexx Jul 09 '12 at 12:47