Click here to Skip to main content
15,880,469 members
Articles / Programming Languages / C#
Tip/Trick

Mocking EF DbContext and DbContextTransaction with Proxy

Rate me:
Please Sign up or sign in to vote.
4.25/5 (4 votes)
12 Oct 2016CPOL1 min read 26.6K   7   2
How to mock EF DbContext and DbContextTransaction with Proxy

Introduction

If you use DbContextTransaction through DbContext.Database.BeginTransaction() in your context, you can't mock it. Because DbContextTransaction has just internal constructors. Therefore, you will get this error System.NotSupportedException: Parent does not have a default constructor. The default constructor must be explicitly defined.

I have already published solution for this problem which uses adapter pattern. It gives you full control of DbContext.Database. But if you don't need full control of DbContext.Database, it's over-engineering.

I will solve the same problem with using proxy pattern.

Background

Proxy

Provide a surrogate or placeholder for another object to control access to it.

Using the Code

First, I will show you how to create a proxy for DbContextTransaction. Then, I will show you how we implement this proxy in our context. After that, I will show you usage with an example. At last, I will show you how to mock easily.

Create Proxy and Implement in DbContext

We create a proxy to access control of DbContextTransaction.

C#
public interface IDbContextTransactionProxy : IDisposable
 {
     void Commit();

     void Rollback();
 }

 /// <summary>
 /// This is proxy. We want accessing control of DbContextTransaction class.
 /// Because we can't write unit test for BeginTransaction.
 /// DbContextTransaction does not have public constructors.
 /// </summary>
 public class DbContextTransactionProxy : IDbContextTransactionProxy
 {
     /// <summary>
     /// Real Class which we want to control.
     /// We can't mock it's because it does not have public constructors.
     /// </summary>
     private readonly DbContextTransaction _transaction;

     public DbContextTransactionProxy(DbContext context)
     {
         _transaction = context.Database.BeginTransaction();
     }

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

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

     public void Dispose()
     {
         _transaction.Dispose();
     }
 }

When DbContextTransactionProxy is constructed, it creates a new DbContextTransaction and manages its control. Therefore, we can mock DbContextTransactionProxy beside of DbContextTransaction.

Let's implement this proxy in our context.

C#
public interface IBrokerContext : IDisposable
{
    DbSet<Customer> Customers { get; set; }
    int SaveChanges();
    IDbContextTransactionProxy BeginTransaction();
    int ExecuteSqlCommand(string sql, params object[] parameters);
 }

public class BrokerContext : DbContext, IBrokerContext
{
 .
 .
 .

  public DbSet<Customer> Customers { get; set; }

  /// <summary>
  /// When we call begin transaction.
  /// Our proxy creates new Database.BeginTransaction and
  /// gives DbContextTransaction's control to proxy.
  /// We do this for unit test.
  /// </summary>
  /// <returns>Proxy which controls DbContextTransaction(Ef transaction class)
  /// </returns>
  public IDbContextTransactionProxy BeginTransaction()
  {
     return new DbContextTransactionProxy(this);
  }

  public int ExecuteSqlCommand(string sql, params object[] parameters)
  {
     return Database.ExecuteSqlCommand(sql, parameters);
  }
}

Using Proxy in Code

Here, we add balance to customer. Then, we execute a SQL command and we want both in a transaction.

C#
public class CustomerService
 {
   private readonly IBrokerContext _context;

   public CustomerService(IBrokerContext context)
   {
     _context = context;
   }

   public void Give5DolarAndMakeCustomerHappy(string customerName)
   {
      //Here we begin a transaction through proxy.
      using (var transaction = _context.BeginTransaction())
      {
        try
          {
            var customer = _context.Customers.Single(x => x.Name == customerName);
            customer.Feels = "Happy";
            //Here we run execute sql command which context can't track.
            //That's why we use transaction.
            _context.ExecuteSqlCommand
            ("Update customers set balance=balance+5 where id=@p0", customer.Id);
            _context.SaveChanges();
            transaction.Commit();
           }
              catch (Exception e)
           {
                  transaction.Rollback();
           }
       }
    }
  }

Mocking DbContext and DbContextTransactionProxy

We will not get any error because now we mock DbContextTransactionProxy instead of DbContextTransaction.

C#
var mockedBrokerContext = new Mock<IBrokerContext>();
var mockedDbTransaction = new Mock<IDbContextTransactionProxy>();
mockedBrokerContext.Setup
      (x => x.BeginTransaction()).Returns(mockedDbTransaction.Object);

Conclusion

I have showed here how to mock DbContextTransaction which is Entity Framework library. But you can apply this solution for all 3rd libraries which you can't mock or it's very hard to mock.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Turkey Turkey
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMocked method should be virtual Pin
Member 1447228414-Jul-20 1:04
Member 1447228414-Jul-20 1:04 
GeneralMy vote of 5 Pin
Ayren Jagar14-Jul-17 5:17
Ayren Jagar14-Jul-17 5:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.