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?
What is Database Mocking? Database Mocking is a technique that allows you to set the desired database state (for different tables) in your tests to let specific data sets ready for future test execution.
Mocking and stubbing are the cornerstones of having quick and simple unit tests. Mocks are useful if you have a dependency on an external system, file reading takes too long, the database connection is unreliable, or if you don't want to send an email after every test.
Without taking the mocks you will not know how good/bad your preparation is. You can take the mocks and make strategies, plan your study schedule and make your preparation to the mark. Practice makes man perfect and for competitive exams, mocks are what make a difference between any candidate and the topper.
Mocking is a process that allows you to create a mock object that can be used to simulate the behavior of a real object. You can use the mock object to verify that the real object was called with the expected parameters, and to verify that the real object was not called with unexpected parameters.
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.
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();
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:
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; };
}
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