【问题标题】:Can I create a mock db context which I can add to, then search?我可以创建一个可以添加然后搜索的模拟数据库上下文吗?
【发布时间】:2015-04-10 14:04:42
【问题描述】:

我有一个简单的文档管理器,它被注入到我在 asp.net c# MVC 项目中的控制器中。该项目是数据库优先的,Document 表由documentId 索引,这是一个自动递增的整数。

我一直在尝试编写一个测试来测试CreateNewDocument 的以下实现,在成功添加文档后查找它并返回新的文档ID。

问题是我找不到模拟 MyEntityFrameWorkEntities 的方法,我可以向其中添加文档,然后使用 linq 搜索该文档。我认为它不起作用,因为被嘲笑的 _context.Document.Add 并没有真正做任何事情。

我的问题是:我可以设置不同的模拟,这样我就可以保留 DocumentManager 并编写一个通过的测试吗?

public class DocumentManager : IDocumentManager
{
    private readonly MyEntityFrameWorkEntities _context;

    public DocumentManager(MyEntityFrameWorkEntities context)
    {
        _context = context;
    }

    public int CreateNewDocument(int userId)
    {
        var newDocumentGuid = Guid.NewGuid(); 
        var newDocument = new Document
        {
            UserId = userId,
            DateCreated = DateTime.Now,
            DocumentGuid = newDocumentGuid
        };
        _context.Document.Add(newDocument);
        _context.SaveChanges();
        // the .First here doesn't return anything when called from tests
        return _context.Document.First(d => d.DocumentGuid == newDocumentGuid).DocumentId;
    }
}

public partial class MyEntityFrameWorkEntities : DbContext
{
    public MyEntityFrameWorkEntities() : base("name=MyEntityFrameWorkEntities")
    {
    }

    public virtual DbSet<Document> Document { get; set; }
    /* ...etc... */
}

和测试类:

[TestMethod]
public void TestCreateNewDocument()
{
    var mockContext = new Mock<MyEntityFrameWorkEntities>();

    var mockDocumentDbSet = GetQueryableMockDocumentDbSet();

    mockContext.Setup(m => m.Document).Returns(mockDocumentDbSet.Object);

    var documentManager = new DocumentManager(mockContext.Object);

    var newDocId = documentManager.CreateNewDocument(123);

    // This line doesn't get hit as the .First falls over before here
    Assert.AreNotEqual(newDocId, 0);
}

private static Mock<DbSet<Document>> GetQueryableMockDocumentDbSet()
{
    var data = new List<Document> { GetDocument(111, 11), GetDocument(222, 22), GetDocument(333, 33) }.AsQueryable();
    var mockDocumentDbSet = new Mock<DbSet<Document>>();
    mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Provider).Returns(data.Provider);
    mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Expression).Returns(data.Expression);
    mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.ElementType).Returns(data.ElementType);
    mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
    return mockDocumentDbSet;
}

private static Document GetDocument(int documentId, int userId)
{
    return new Document
    {
        DocumentId = documentId,
        UserId = userId,
        DateCreated = DateTime.Now.AddDays(-1),
        DocumentGuid = Guid.NewGuid(),
    };
}

【问题讨论】:

    标签: c# entity-framework unit-testing model-view-controller mocking


    【解决方案1】:

    您可以使用回调设置模拟 DbSet 的 Add() 方法,该回调会将项目添加到您的支持列表中:

    private static Mock<DbSet<Document>> GetQueryableMockDocumentDbSet()
    {
        var data = new List<Document> { GetDocument(111, 11), GetDocument(222, 22), GetDocument(333, 33) };
    
        var mockDocumentDbSet = new Mock<DbSet<Document>>();
        mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Provider).Returns(data.AsQueryable().Provider);
        mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.Expression).Returns(data.AsQueryable().Expression);
        mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.ElementType).Returns(data.AsQueryable().ElementType);
        mockDocumentDbSet.As<IQueryable<Document>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
        mockDocumentDbSet.Setup(m => m.Add(It.IsAny<Document>())).Callback<Document>(data.Add);
        return mockDocumentDbSet;
    

    }

    您随后对 First() 的调用应该能够检索该项目。

    【讨论】:

    • 这完全符合我的预期,谢谢。
    • 干杯。这确实救了我的命。
    • 这不适用于像“FirstOrDefaultAsync”这样的异步调用。源 IQueryable 的提供程序不实现 IDbAsyncQueryProvider。使用此链接修复它:msdn.microsoft.com/en-us/data/dn314429#async
    • 您还应该为 AddRange 设置回调
    • 如果您遇到模拟 DBSet 的“枚举没有结果”的问题,请确保对 GetEnumerator() 使用 linq 语句,而不是每次调用都返回相同的 Enumerator,这最终会耗尽:mockDocumentDbSet.As&lt;IQueryable&lt;Document&gt;&gt;().Setup(m =&gt; m.GetEnumerator()).Returns(() =&gt; data.GetEnumerator());
    【解决方案2】:

    考虑在更高的抽象层进行模拟。 在这种情况下,请考虑模拟存储库。 你甚至可以更进一步并模拟服务本身。

    Construct testable business layer logic

    【讨论】:

      猜你喜欢
      • 2015-09-11
      • 2020-12-04
      • 2014-10-06
      • 1970-01-01
      • 2017-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-10
      相关资源
      最近更新 更多