问题
Background: - Using EntityFramework 6 - Using Moq v4.2.1402.2112 - Using DbFirst methodology
I have been following the EF6 Moq walkthrough (which can be found here) however; I have been wondering if it is possible to have a mocked DbSet retain the data that is added to it for the duration of it's scope?
For example:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace TestingDemo
{
[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);
service.AddBlog("YYY", "http://blogs.msdn.com/yyyy");
var blogs = service.GetAllBlogs();
Assert.AreEqual(4, blogs.Count); // Fails because whilst the AddBlog is called, and we can verify this, the AsQueryable List doesn't retain the new data
Assert.AreEqual("AAA", blogs[0].Name);
Assert.AreEqual("BBB", blogs[1].Name);
Assert.AreEqual("YYY", blogs[2].Name);
Assert.AreEqual("ZZZ", blogs[3].Name);
}
}
}
Is it possible to have the added data retained so it can be queried later in the test?
回答1:
What you need to do is to use callbacks for all the methods that you expect to modify the collection. A callback in a dynamic proxy is code that runs when a method is called, and has access to the parameters.
Create an ad hoc collection to hold the data (like the data
in your sample code). Then implement a callback (1): for each method of the dynamic proxy that can modify the collection. In these callback, modify your ad hoc collection (data
). Then in your dynamic proxy, mock the Count
method to return the count from this collection (data
).
Your ad hoc collection is already created: data
.
(2)The method to add a callback to is the one used by your service to add the blog, BlogService.AddBlog
I don't use Moq4 so I won't show you the syntax, but look here, in the callbacks section, and use this syntax to add the required callbacks.
The problem with mocking something with many methods and properties and complex behavior like a DbSet<T>
is:
- that you need to implement a lot of callback in your dyanmic proxy: How many methods are that can modify the collections, as stated in (1)?
- or you need to know details of the implementation of the code under test, to implement only the necessary callbacks, like in (2) Oops! That's ugly. You can implement something that works, but the test can fail because you're not mocking the required methods.
To avoid this problem you have two solutions.
- Create an interface to wrap the EF functionality. Mock this interface. This solution limits the number of methods to mock
- Use an in-memory database as your backend, so that your testing resembles a real DB and you need to mock nothing. You only need to initialize the in-memory db data for each test. Look here, but don't look only the accepted answer (which BTW is not the best one).
来源:https://stackoverflow.com/questions/24258910/entity-framework-6-and-moq4-is-it-possible-to-have-a-mocked-dbset-retain-added