mar*_*c_s 5 c# unit-testing moq mocking entity-framework-6
我正在使用Entity Framework(针对SQL Server数据库)为我的ASP.NET MVC应用程序编写一堆单元测试.
我正在使用Rowan Miller优秀的Nuget软件包"EntityFramework.Testing"和"EntityFramework.Testing.Moq"来允许我对EF代码进行单元测试(实际上没有真正的SQL Server数据库).
这是我的NUnit 3.5测试夹具(实际上,它有更多的测试 - 但它只是为了展示如何设置):
[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);
}
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,在[SetUp]方法中,我正在调用SetupTestData哪个正在创建Mock<MyModel>用于模拟整个DbContext,并设置一个MockDbSet<Contact>来处理我的联系人.
大多数测试对这种设置都很好 - 直到我在SaveContact这里遇到这个方法:
public void SaveContact(Contact contactToSave, bool isNew) {
if (isNew) {
ModelContext.Contact.Add(contactToSave);
} else {
ModelContext.Entry(contactToSave).State = EntityState.Modified;
}
ModelContext.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,如果我正在尝试保存Contact已存在的,我正在做的就是设置它的State标志Modified并让EF处理所有其余的.
伟大工程在运行时 -但在这里的测试中,它会导致测试代码要连接到数据库-我没有在眼前.
那么我还需要做些什么才能使用我的EF Mocking基础设施对这行代码进行单元测试呢?它可以完成吗?
ModelContext.Entry(contactToSave).State = EntityState.Modified;
Run Code Online (Sandbox Code Playgroud)
DbContext.Entry不是虚拟的,因此起订量无法覆盖它。
您基本上是在尝试对 EF 进行单元测试,这是 Microsoft 在发布之前就已经完成的。最好使用实际的支持数据库来执行 EF 集成测试。
尽管如此,您可以考虑抽象对模型的访问。
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
}
Run Code Online (Sandbox Code Playgroud)
并让你的上下文实现从中派生
public partial class MyModel : DbContext, IMyModelContext {
//...
}
Run Code Online (Sandbox Code Playgroud)
类应该依赖于抽象而不是具体。
public class ContactsUseCase {
private readonly IMyModelContext ModelContext;
public ContactsUseCase(IMyModelContext context) {
ModelContext = context;
}
//...
}
Run Code Online (Sandbox Code Playgroud)
您仍然可以使用模拟包来模拟您的数据库集,但现在您也可以灵活地正确模拟上下文。
[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());
}
}
Run Code Online (Sandbox Code Playgroud)