单元测试EF的状态管理代码

时间:2017-11-20 13:18:05

标签: c# unit-testing mocking entity-framework-6 moq

我正在使用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);
    }
}

正如您所看到的,在[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();
}

正如您所看到的,如果我正在尝试保存已存在的Contact,我正在做的就是将State标志设置为Modified并让EF处理所有其余的。

在运行时运行良好 - 但在测试中,它会导致测试代码想要连接到数据库 - 我手头没有。

那么我还需要做些什么才能使用我的EF Mocking基础设施对这行代码进行单元测试?它可以完成吗?

ModelContext.Entry(contactToSave).State = EntityState.Modified;

1 个答案:

答案 0 :(得分:1)

DbContext.Entry不是虚拟,因此moq无法覆盖它。

您基本上是在尝试对EF进行单元测试,而微软在发布之前就会对其进行测试。最好使用实际的后备数据库与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
}

并让您的上下文实现从中派生

public partial class MyModel : DbContext, IMyModelContext {
    //...
}

类应该依赖于抽象而不是结核。

public class ContactsUseCase {
    private readonly IMyModelContext ModelContext;

    public ContactsUseCase(IMyModelContext context) {
        ModelContext = context;
    }

    //...
}

您仍然可以使用模拟包来模拟您的数据库集,但现在您也可以灵活地正确模拟上下文。

[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());
    }
}