【问题标题】:Why is my mock set empty?为什么我的模拟集是空的?
【发布时间】:2015-06-15 12:58:51
【问题描述】:

我刚刚开始学习单元测试和模拟。我花了一整天的时间阅读不同的教程,试图找到最好的一个来练习。我已经选择了Testing with a mocking framework (EF6 onwards),因为它使用的是 EF6(现代),以及似乎非常流行的模拟框架(Moq)。此外,它非常普通,托管在 MSDN 网络集上。必须得体,对吧?

我已经完全按照示例中的说明设置了一个项目,并且正在通过测试示例运行调试器,以确保我了解正在发生的事情。我正在进行的测试如下:

[TestClass] 
public class QueryTests 
{ 
    [TestMethod] 
    public void GetAllBlogs_orders_by_name() 
    { 
        var data = new List<Blog> 
        { 
            new Blog { Name = "BBB" }, 
            new Blog { Name = "ZZZ" }, 
            new Blog { Name = "AAA" }, 
        }.AsQueryable(); 

        var mockSet = new Mock<DbSet<Blog>>(); 
        mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
        mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
        mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
        mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); 

        var mockContext = new Mock<BloggingContext>(); 
        mockContext.Setup(c => c.Blogs).Returns(mockSet.Object); 

        var service = new BlogService(mockContext.Object); 
        //test code here
        var blogs = service.GetAllBlogs(); 

        Assert.AreEqual(3, blogs.Count); 
        Assert.AreEqual("AAA", blogs[0].Name); 
        Assert.AreEqual("BBB", blogs[1].Name); 
        Assert.AreEqual("ZZZ", blogs[2].Name); 
    } 
} 

这很简单,我相信我正在理解单元测试和模拟框架。凉爽的!我决定通过在服务实例化之后将service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet");(来自前面的示例)插入到上面的TestMethod 中来执行一个实验来验证我自己。

我希望当我越过var blogs = service.GetAllBlogs(); 时,博客应该包含我的新条目,但事实并非如此。它只包含data的初始化中的3个。

这里发生了什么?该代码不应该将博客记录插入data 对象,从而在调用GetAllBlogs() 时将其拉出吗?也许我没有正确理解模拟的想法?

【问题讨论】:

  • 模拟DbSet&lt;T&gt; 是出了名的困难。我强烈建议使用存储库模式并测试存储库,或者将存储库模式与内存数据库结合使用,您可以使用 Effort 轻松实现。在my blog 上了解更多关于后一种技术的信息。我非常不鼓励尝试模拟所有与 EF 相关的内容,因为它会将您的代码变成意大利面条。

标签: c# asp.net-mvc unit-testing moq


【解决方案1】:

为什么我的模拟集是空的?

因为您没有配置(设置)它在您向其插入数据时执行任何操作。创建模拟时,它会“丢失”原始类中存在并标记为virtual 的所有逻辑。 Moq 只是简单地覆盖这些方法,以便稍后您可以将它们配置为执行任何操作(返回值、抛出等)。

当然,您可以设置Add 方法将数据插入到您的data 博客列表中:

var mockSet = new Mock<DbSet<Blog>>();
mockSet.Setup(m => m.Add<It.IsAny<Blog>()).Callback(blog => data.Add(blog));

Add 方法将在您的DbSet 上调用时(最好通过调用服务)已配置 版本的Add 将接管并将博客插入您的数据列表 (Callback方法告诉 Moq 在调用模拟方法时“调用此代码”)。

您还可以尝试通过使用CallBase 参数来防止覆盖Add。但是,这可能不起作用,因为您的 DbSet 不知道 list 的存在,那么您很可能需要对其进行更多配置。

最后一点,一旦您的实验具体化,您应该意识到配置 Add 将不是必需的,因为您不会检查是否通过 GetAllBlogs 方法添加了博客,而是通过验证模拟本身:

mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());

【讨论】:

  • 有趣...我想这是有道理的,也许我误解了模拟的想法/目的。你愿意详细说明嘲笑的目的吗?看来我所做的只是检查Add 是否实际上是在Blog 对象上调用的。可能是因为示例的简单性,但这似乎不是一个非常有价值的测试。我也愿意参考链接。
  • @Jeff:你的嘲笑想法是 +/- 正确的。就GetAllBlogs test 而言,您正在做的事情或多或少是正确的。自然,验证期间的详细程度可能会有所不同(毕竟,您将检查您刚刚在上面创建的 2 行的对象)——您可以断言列表引用、列表计数、每个列表元素引用等。@987654338另一方面,@(可能)不返回任何内容,并且它的“工作”对外界不可见——这就是为什么你通过 Verify 方法断言的原因,在这种情况下这是很自然的方法。
【解决方案2】:

您是否尝试使用 CallBase 属性?

mockSet.CallBase = true;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-16
    相关资源
    最近更新 更多