【问题标题】:Unit Testing FindAsync单元测试 FindAsync
【发布时间】:2013-12-21 08:42:32
【问题描述】:

我已经使用这篇出色的 MSDN 文章中的异步查询提供程序设置了一个测试项目:http://msdn.microsoft.com/en-US/data/dn314429#async,效果很好。

但是当我添加一个调用FindAsync的方法时:

public async Task<Blog> GetBlog(int blogId)
{
    return await _context.Blogs.FindAsync(blogId);
}

并在格式中添加以下单元测试:

[TestMethod]
public async Task GetAllBlogsAsync_gets_blog()
{
    var data = new List<Blog>
    {
        new Blog { BlogId = 1, Name = "BBB" },
        new Blog { BlogId = 2, Name = "ZZZ" },
        new Blog { BlogId = 3, Name = "AAA" },
    }.AsQueryable();

    var mockSet = new Mock<DbSet<Blog>>();
    mockSet.As<IDbAsyncEnumerable<Blog>>()
        .Setup(m => m.GetAsyncEnumerator())
        .Returns(new TestDbAsyncEnumerator<Blog>(data.GetEnumerator()));

    mockSet.As<IQueryable<Blog>>()
        .Setup(m => m.Provider)
        .Returns(new TestDbAsyncQueryProvider<Blog>(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);
    var blog = await service.GetBlog(2);

    Assert.AreEqual("ZZZ", blog.Name);
}

但是,当从我的测试方法中调用 GetBlog 时,await _context.Blogs.FindAsync(blogId); 会抛出 NullReferenceExceptionTestingDemo.BlogService.&lt;GetBlog&gt;d__5.MoveNext()

有什么建议可以使用 MSDN 文章中的测试方法对调用 FindAsync 的方法实施单元测试:http://msdn.microsoft.com/en-US/data/dn314429#async

【问题讨论】:

  • 您确定问题不在于您的模拟设置吗?这看起来是最复杂的部分,并且可能容易受到NullReferenceExceptions 的影响。
  • @Tim S - 你可能是对的,但它与 MSDN 文章底部用于 GetAllBlogsAsync 的模拟设置几乎相同,效果很好。
  • 测试外部依赖不仅非常困难(正如您从正在创建的模拟对象的汤中看到的那样),它的价值也值得怀疑。您不仅要测试外部依赖项,还要在综合条件下对其进行测试,因为您没有调用实际的数据库,并且您的测试场景中存在很多活动部分,而这些部分在您的生活中并不存在场景(反之亦然)。

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


【解决方案1】:

MoveNext 中的MoveNext 中的async 方法几乎总是由于从另一个异步方法返回null

在这种情况下,FindAsync 似乎正在返回 null,这是有道理的,因为我看不出你在哪里嘲笑它。您目前正在嘲笑IQueryableGetAsyncEnumerator 方面,但不是FindAsync。您发布的示例文章未提供完整的DbSet 模拟解决方案。

【讨论】:

  • 当然!我天真地认为这篇文章提供了一个完整的实现。做类似的事情:mockSet.Setup(t =&gt; t.FindAsync(It.IsAny&lt;int&gt;())).Returns(Task.FromResult(fakeBlog)); 似乎有效 - 你的意思是这样吗?
  • @Andy:是的,我就是这个意思。
  • @Andy 你评论中的 fakeBlog 是什么?
  • @LJNielsenDk 这是我们在这里创建的假博客,例如var fakeBlog = new Blog { BlogId = 7, Name = "Fake Blog" }; 然后设置 FindAsync 方法在我们的测试期间返回这个假博客(使用上面评论中的代码)。
  • @StephenCleary 谢谢!我面临着同样的问题。但是现在,如果我这样做了,mockSet.Setup(c =&gt; c.FirstOrDefaultAsync(It.IsAny&lt;Expression&lt;Func&lt;UserAccount, bool&gt;&gt;&gt;())).Returns(Task.FromResult&lt;UserAccount&gt;(localUserAccount)); 我有一个例外说,Invalid setup on an extension method: c =&gt; c.UserAccount.FirstOrDefaultAsync&lt;UserAccount&gt;(It.IsAny&lt;Expression1>())` 我终其一生都无法弄清楚出了什么问题。 FirstOrDefault 不是扩展方法,它是通过接口在基类中实现的。
【解决方案2】:

我也遇到过这个问题。还有另一种解决方案,它既快速又简单,尤其是在您只期望一个结果并且不需要依赖于上下文缓存的情况下。也就是使用 SingleOrDefaultAsync 而不是 FindAsync。

旧版本:

public async Task<Blog> GetBlog(int blogId)
{
    return await _context.Blogs.FindAsync(blogId);
}

新版本:

public async Task<Blog> GetBlog(int blogId)
{
    return await _context.Blogs.Where(b => b.BlogId == blogId).SingleOrDefaultAsync();
}

此版本将与您拥有的模拟代码一起使用。

(这篇文章的答案解释了不同之处:Use of Include with async await

【讨论】:

  • 应该有一个很好的方法来实现这一点。 FindAsync 方法将首先查看实体是否已加载并返回它。如果没有,它将获取数据库。按照您的建议执行查询将始终从数据库中获取。
  • 您正在更改实现而不仅仅是语法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 2011-07-07
  • 2011-04-12
  • 2023-03-21
  • 2011-05-04
相关资源
最近更新 更多