I'm writing a bunch of unit tests for my ASP.NET MVC app using Entity Framework (against a SQL Server database).
I'm using Rowan Miller's excellent Nuget packages "EntityFramework.Testing" and "EntityFramework.Testing.Moq" to allow me to unit test EF code (without actually having a real SQL Server database around).
This is my NUnit 3.5 test fixture (in reality, it has a lot more tests - but it's just to show how things are set up):
[TestFixture]
public class ContactsUseCaseTests : MyUnitTestBase
{
private Mock<MyModel> _mockDbContext;
private MockDbSet<Contact> _mockDbSetContact;
private IContactsUseCase _usecase;
[SetUp]
public void InitializeTest()
{
SetupTestData();
_usecase = new ContactsUseCase(_mockDbContext.Object);
}
[Test]
public void TestSaveEntryNotNewButNotFound()
{
// Arrange
Contact contact = new Contact { ContactId = 99, FirstName = "Leo", LastName = "Miller" };
// Act
_usecase.SaveContact(contact, false);
// Assert
_mockDbSetContact.Verify(x => x.Add(It.IsAny<Contact>()), Times.Once);
_mockDbContext.Verify(x => x.SaveChanges(), Times.Once);
}
private void SetupTestData()
{
var contacts = new List<Contact>();
contacts.Add(new Contact { ContactId = 12, FirstName = "Joe", LastName = "Smith" });
contacts.Add(new Contact { ContactId = 17, FirstName = "Daniel", LastName = "Brown" });
contacts.Add(new Contact { ContactId = 19, FirstName = "Frank", LastName = "Singer" });
_mockDbSetContact = new MockDbSet<Contact>()
.SetupAddAndRemove()
.SetupSeedData(contacts)
.SetupLinq();
_mockDbContext = new Mock<MyModel>();
_mockDbContext.Setup(c => c.ContactList).Returns(_mockDbSetContactList.Object);
_mockDbContext.Setup(c => c.Contact).Returns(_mockDbSetContact.Object);
}
}
As you can see, in the [SetUp]
method, I'm calling SetupTestData
which is creating the Mock<MyModel>
for mocking the entire DbContext
, and it sets up a MockDbSet<Contact>
to handle my contacts.
Most tests works just fine against this setup - until I came across the SaveContact
method here:
public void SaveContact(Contact contactToSave, bool isNew) {
if (isNew) {
ModelContext.Contact.Add(contactToSave);
} else {
ModelContext.Entry(contactToSave).State = EntityState.Modified;
}
ModelContext.SaveChanges();
}
As you can see, if I'm trying to save a Contact
that already exists, all I'm doing is setting it's State
flag to Modified
and letting EF handle all the rest.
Works great at runtime - but here in the test, it causes the test code to want to connect to the database - which I don't have at hand.
So what do I need to do additionally to make it possible to unit-test this line of code using my EF Mocking infrastructure? Can it be done at all?
ModelContext.Entry(contactToSave).State = EntityState.Modified;
DbContext.Entry
is not virtual so moq is unable to override it.
You are basically trying to unit test EF, which Microsoft would have done before release. It is better to perform integration tests with EF using an actual backing database.
That said however, you could consider abstracting the access to your model.
public interface IMyModelContext : IDisposable {
DbSet<Contact> Contact { get; }
int SaveChanges();
DbEntityEntry Entry(object entity);
DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
//..other needed members
}
and having your context implementation derive from it
public partial class MyModel : DbContext, IMyModelContext {
//...
}
Classes should depend on abstractions and not on concretions.
public class ContactsUseCase {
private readonly IMyModelContext ModelContext;
public ContactsUseCase(IMyModelContext context) {
ModelContext = context;
}
//...
}
You can still use the mocking package to mock your db sets, but now you have the flexibility to properly mock the context as well.
[TestFixture]
public class ContactsUseCaseTests : MyUnitTestBase {
private Mock<IMyModelContext> _mockDbContext;
private MockDbSet<Contact> _mockDbSetContact;
private IContactsUseCase _usecase;
[SetUp]
public void InitializeTest() {
SetupTestData();
_usecase = new ContactsUseCase(_mockDbContext.Object);
}
[Test]
public void TestSaveEntryNotNewButNotFound() {
// Arrange
Contact contact = new Contact { ContactId = 99, FirstName = "Leo", LastName = "Miller" };
// Act
_usecase.SaveContact(contact, false);
// Assert
_mockDbSetContact.Verify(x => x.Add(It.IsAny<Contact>()), Times.Never);
_mockDbContext.Verify(x => x.SaveChanges(), Times.Once);
}
private void SetupTestData() {
var contacts = new List<Contact>();
contacts.Add(new Contact { ContactId = 12, FirstName = "Joe", LastName = "Smith" });
contacts.Add(new Contact { ContactId = 17, FirstName = "Daniel", LastName = "Brown" });
contacts.Add(new Contact { ContactId = 19, FirstName = "Frank", LastName = "Singer" });
_mockDbSetContact = new MockDbSet<Contact>()
.SetupAddAndRemove()
.SetupSeedData(contacts)
.SetupLinq();
_mockDbContext = new Mock<IMyModelContext>();
_mockDbContext.Setup(c => c.ContactList).Returns(_mockDbSetContactList.Object);
_mockDbContext.Setup(c => c.Contact).Returns(_mockDbSetContact.Object);
_mockDbContext.Setup(c => c.Entry(It.IsAny<Contact>()).Returns(new DbEntityEntry());
}
}
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