8

I've set up a test project using the async query provider found in this excellent MSDN article: http://msdn.microsoft.com/en-US/data/dn314429#async which works great.

However when I add a method which calls FindAsync:

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

And add the following unit test in the format:

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

However when GetBlog is called from my test method, await _context.Blogs.FindAsync(blogId); throws a NullReferenceException at TestingDemo.BlogService.<GetBlog>d__5.MoveNext()

Any suggestions how I can implement unit tests on methods calling FindAsync using the testing methodology found in the MSDN article: http://msdn.microsoft.com/en-US/data/dn314429#async ?

Andy
  • 7,646
  • 8
  • 46
  • 69
  • Are you sure the problem isn't with your mocking setup? That looks like the most complicated part, and potentially susceptible to `NullReferenceExceptions`. – Tim S. Dec 04 '13 at 18:09
  • @Tim S -You may well be right, but it's almost identical mocking setup to that used for GetAllBlogsAsync at the bottom of the MSDN article which works fine. – Andy Dec 04 '13 at 18:21
  • Testing external dependencies is not only really tough (as you can see from the soup of mock objects you're creating), it is of questionable value. You're not only testing an external dependency, you're testing it under synthetic conditions, as you're not calling out to an actual database and there's a lot of moving parts that exist in your test scenario that don't in your live scenario (and vice versa). – 48klocs Dec 04 '13 at 18:27

2 Answers2

12

NullReferenceException inside a MoveNext for an async method is almost always due to returning null from another async method.

In this case, it looks like FindAsync is returning null, which makes sense since I don't see where you're mocking it. You're currently mocking the IQueryable and GetAsyncEnumerator aspects, but not FindAsync. The example article you posted does not provide a complete DbSet mocking solution.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 7
    Of course! I'd naively assumed that the article had supplied a full implementation. Doing something like: `mockSet.Setup(t => t.FindAsync(It.IsAny())).Returns(Task.FromResult(fakeBlog));` seems to work - is that the kind of thing you meant? – Andy Dec 05 '13 at 12:27
  • 2
    @Andy: Yes, that's what I meant. – Stephen Cleary Dec 05 '13 at 13:46
  • @Andy What is fakeBlog in your comment? – LJNielsenDk Jul 09 '15 at 15:53
  • 1
    @LJNielsenDk It is a fake Blog that we create here e.g. var `fakeBlog = new Blog { BlogId = 7, Name = "Fake Blog" };` and then set up the FindAsync method to return this fake blog (using the code in the comment above) during our test. – Andy Jul 09 '15 at 16:22
  • @StephenCleary Thanks! I was facing the same issue. But now, if I do that, `mockSet.Setup(c => c.FirstOrDefaultAsync(It.IsAny>>())).Returns(Task.FromResult(localUserAccount));` I had an exception saying, `Invalid setup on an extension method: c => c.UserAccount.FirstOrDefaultAsync(It.IsAny())` I can't figure out for the life of me what's wrong. FirstOrDefault is not an extension method, it's implemented in base class from an interface. – Razort4x Sep 28 '17 at 12:05
  • @Razort4x: Sorry, it's been too long since I've mocked async EF. I recommend posting your own question on SO. – Stephen Cleary Sep 28 '17 at 15:15
0

I've run across this issue too. There is another solution, that is quick and easy especially where you expect only a single result, and don't need to depend on the context caching. That is to use SingleOrDefaultAsync instead of FindAsync.

Old version:

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

New version:

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

This version will work with the mocking code you have.

(The answer to this post explains the difference: Use of Include with async await )

Community
  • 1
  • 1
dylanT
  • 1,080
  • 1
  • 13
  • 26
  • 2
    There should be a good way to achieve this. The FindAsync method will first look if the entity is already loaded and return it. If not, it will fetch the database. Performing a query as you suggested will always fetch from the database. – Jacques Bourque Mar 20 '15 at 23:08
  • 1
    You're changing the implementation not just syntax. – Max Dec 21 '15 at 20:04