Mocking Database transactions?

19,905

Solution 1

Testing this kind of stuff is always complicated, but first of all you should ask yourself if you want to unit test your business logic or if you want to integration test your application.

If you want to unit test your logic, you basically shouldn't even try to mock entity framework, because you do not want to test EF, you just want to test your code, right? To do so, mock any data access object and only unit test your business logic.

But if you want to test if your data access layer works, e.g. if your code can handle all the CRUD operations you have implemented, you should do integration tests against a real database. Do not try to mock any data access objects (EF) in this case, simply run you tests against a test database or a sql-express localDB for example.

Solution 2

You can wrap the context and the transaction in an Interface and then implement the interface by some provider class:

public interface IDbContextProvider
{
    YourContext Context { get; set; }
    DbContextTransaction DbTransaction { get; set; }
    void Commit();
    void Rollback();
    void BeginTransaction();
    void SaveChanges();
}

and then implement it:

public class EfContextProvider : IDbContextProvider
{
    public EfContextProvider(YourContext context)
    {
        Context = context;
    }
    public YourContext Context { set; get; }
    public DbContextTransaction DbTransaction { set; get; }

    public void Commit()
    {
        DbTransaction.Commit();
    }

    public void Rollback()
    {
        DbTransaction.Rollback();
    }

    public void BeginTransaction()
    {
        DbTransaction=Context.Database.BeginTransaction();
    }

    public void SaveChanges()
    {
        Context.SaveChanges();
    }
}

so now give your class the IDbContextProvider dependency and work with it (It has also the context inside) . Maybe substitute the using block with _contextProvider.BeginTransaction(); and then also _contextProvider.Commit(); or _contextProvider.Rollback();

Solution 3

I have spent few hours trying to figure it out, I believed it can be done by MS Fakes directly without wrapper or new classe.

You need to do three steps:

  1. Create shim object for DbContextTransaction, and detour its Commit and Rollback methods to do nothing.
  2. Create shim object for Database. And detour its BeginTransaction method to return DbContextTransaction shim object created in step 1.
  3. Detour DbContext.Database property for all instances to return Database shim object created in step 2.

And that all.

    static void SetupDBTransaction()
    {
        System.Data.Entity.Fakes.ShimDbContextTransaction transaction = new System.Data.Entity.Fakes.ShimDbContextTransaction();
        transaction.Commit = () => { };
        transaction.Rollback = () => { };

        System.Data.Entity.Fakes.ShimDatabase database = new System.Data.Entity.Fakes.ShimDatabase();
        database.BeginTransactionIsolationLevel = (isolationLevel) =>{return transaction.Instance;};

        System.Data.Entity.Fakes.ShimDbContext.AllInstances.DatabaseGet = (@this) => { return database.Instance; };
    }

Solution 4

You can represent your EF classes as POCO classes and isolate all database interactions in database adapter classes. Those adapter classes would have an interface you could mock when testing business logic.

The database operations in the adapter classes can be tested with a real database connection, but with a dedicated database and connection string for the unit tests.

So how about testing business code wrapped in transactions?

In order to isolate the business code from the database adapters, you would have to create an interface for the EF transaction scope that you can mock.

I have previously worked with a design like this, though not with EF, but with similar POCO wrapping (in pseudo C#, not syntax or sanity checked):

interface IDatabaseAdapter 
{
    ITransactionScope CreateTransactionScope();
}

interface ITransactionScope : IDisposable
{
    void Commit();
    void Rollback();        
}

class EntityFrameworkTransactionScope : ITransactionScope
{
    private DbContextTransaction entityTransaction;
    EntityFrameworkTransactionScope(DbContextTransaction entityTransaction)
    {
        this.entityTransaction = entityTransaction;
    }

    public Commit() { entityTransaction.Commit(); }
    public Rollback() { entityTransaction.Rollback(); }
    public Dispose() { entityTransaction.Dispose(); }

}

class EntityFrameworkAdapterBase : IDatabaseAdapter
{
   private Database database;
   protected EntityFrameworkAdapterBase(Database database)
   {
       this.database = database;
   }

   public ITransactionScope CreateTransactionScope()
   {
       return new EntityFrameworkTransactionScope(database.BeginTransaction());
   }
}

interface IIncidentDatabaseAdapter : IDatabaseAdapter
{
    SaveIncident(Incident incident);
}

public EntityIncidentDatabaseAdapter : EntityFrameworkAdapterBase, IIncidentDatabaseAdapter
{
    EntityIncidentDatabaseAdapter(Database database) : base(database) {}

    SaveIncident(Incident incident)
    {
         // code for saving the incident
    }
}

The above design should allow you to create unit test for entity framework operations without worrying about business logic or transaction and to create unit tests for business logic where you can mock database failures and use MOQ or similar to check that rollback is in fact called on your ITransactionScope mock. With something like the above you should be able to cover pretty much any transaction failure at any stage in the business logic that you can think of.

Of course you should supplement your unit tests with some good integration tests, since transactions can be tricky, especially tricky deadlocks can occur when used concurrently and those would be hard to catch in a mocked test.

Share:
19,905

Related videos on Youtube

Jeff Dege
Author by

Jeff Dege

Updated on September 14, 2022

Comments

  • Jeff Dege
    Jeff Dege over 1 year

    I have a pair of tables with a parent/child relationship - incident and incidentDetails. I have a viewmodel that contains information from both of these tables. And I have a business layer method that is passed an instance of the viewmodel that needs to update both tables.

    So, in the method, I'm using EF6's new transaction mechanism:

    using (var transaction = this.db.Database.BeginTransaction())
    {
        try
        {
            // various database stuff
            this.db.SaveChanges();
            // more database stuff
            this.db.SaveChanges();
            // yet more database stuff
            this.db.SaveChanges();
    
            transaction.Commit();
        }
        catch (Exception ex)
        {
            transaction.Rollback();
            this.logger.logException(ex, "Exception caught in transaction, rolling back");
            throw;
        }
    }
    

    And so, my problem. How do I test this?

    I'm using Microsoft's unit testing framework, with Moq, and I have had no trouble with mocking up DBContexts, and DbSet<>s, but I can't seem to figure out how to get around the transaction stuff.

    If I don't attempt to mock the transaction, I get an InvalidOperationException:

    "No connecting string named xxx could be found in the application config file."

    Which makes perfect sense - there isn't an application config file, and there isn't any database.

    But if I try to mock BeginTransaction(), I get initialization errors: NotSupportedException:

    "Invalid setup on a non-virtual member: m => m.Database.BeginTransaction".

    And that got me chasing into the weeds, looking at decompiles of the .NET methods, trying to identify some class that might derive from a usable interface, or something, where I could somehow inject a mocking object.

    I'm not trying to unit-test MS's transactional code - I just want to make sure that the appropriate changes are made to the appropriate records in each of the tables. But as it sits, it looks like this is non-testable, and that any method that uses transactions is non-testable. And that's just a pain.

    I've Googled around, and not found anything of use. Has anyone run into this issue? Anyone have ideas on how to proceed?

  • Jeff Dege
    Jeff Dege over 10 years
    The problem is that this one method needs to make a series of database calls, in a single transaction. What I want to test is to see that the appropriate calls are made. I'm willing to assume that when I insert or update a record, that the appropriate changes are made to the database. What I am not willing to assume is that when I call the method, the correct series of insert or updates are made.
  • Darin Dimitrov
    Darin Dimitrov over 10 years
    @JeffDege, what you describe is an integration test, not a unit test. A unit test is something that shouldn't depend on any databases. Write an integration test for this stuff.
  • Jeff Dege
    Jeff Dege over 10 years
    If I was talking to a database, yes, it would be an integration test. That's why I'm trying to mock the database.
  • MichaC
    MichaC over 10 years
    @JeffDege To be clear, you simply cannot mock the database and/or transaction. The EF implementation is not mock friendly with non virtual methods and classes without public ctors. And this is for good reason, because usually you should not do something like that. Instead simply use the great features of EF to setup test databases with sqlexpress, this might even initialize the database for you when running unit tests... Or, if you want to unit test your code, refactor your code in a way that it is unit testable and completely isolated from any database interaction.
  • Jeff Dege
    Jeff Dege over 10 years
    I'm beginning to understand that EF is not mock friendly. But I strongly disagree that there is a good reason for that.
  • MERose
    MERose almost 9 years
    What exactly is the question?
  • Jeff Dege
    Jeff Dege almost 9 years
    What I've been doing, lately, is creating a Local(db) database, within the project.
  • Kaboo
    Kaboo almost 9 years
    The question is about how to unit test code that use transactions in Entity Framework. The context class can be mocked easily, but not the transactions or the database.
  • Daniel M
    Daniel M almost 8 years
    @Kaboo, this is excellent, but I'm having issues getting System.Data.Entity.Fakes.ShimDatabase. It should be as simple as right click the System.Data.Entity reference and "add fakes", correct?
  • Daniel M
    Daniel M almost 8 years
    Doh, right click EntityFramework reference, add fakes.
  • user441521
    user441521 over 7 years
    I agree 100% with Jeff here. I run a sproc inside a transaction then I do business logic with an out parameter of a stored proc. I need to test that business logic but I can't because of how transactions are setup in EF? That's garbage.
  • tomRedox
    tomRedox over 5 years
    Should this be made to implement IDisposable so that it can be wrapped in a Using statement when used to ensure the DbContextTransaction is disposed?