Why is my mock set empty?

2019-06-02 21:51发布

I am just starting to learn about unit testing and mocking. I've spent all day reading different tutorials, trying to find the best one to practice with. I've settled on Testing with a mocking framework (EF6 onwards) as it is using EF6 (modern), as well as what seems to be a very popular mocking framework (Moq). Also, it is quite vanilla and hosted on the MSDN webset. It has to be decent, right?

I've set up a project exactly as specified in the example and am running the debugger through the test examples to make sure I understand what is going on. The test I'm working through is as follows:

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

It is very simple and I am lead to believe that I am understanding unit testing and the mocking framework. Cool! I decide to perform an experiment to validate myself, by inserting service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet"); (from a previous example) into the above TestMethod, just after the service is instantiated.

I expect that as I step past var blogs = service.GetAllBlogs();, blogs should contain my new entry, but it does not. It only contains the 3 from the initialization of data.

What is going on here? Shouldn't that code insert a blog record into the data object, and thus pulling it when calling GetAllBlogs()? Perhaps I am not understanding the idea of mocks properly?

2条回答
beautiful°
2楼-- · 2019-06-02 22:05

Why is my mock set empty?

Because you didn't configure (setup) it to take any action when you insert data to it. When a mock is created it "loses" all the logic that was present on the original class and marked virtual. Moq simply overrides such methods so that later you can configure them to do anything (return value, throw, etc).

Naturally, you can setup Add method to insert data back to your data blog list:

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

When Add method will be invoked on your DbSet (preferrably via call to service) configured version of Add will take over and insert blog to your data list (Callback method tells Moq to "invoke this code" when mocked method is called).

You could also try to prevent overriding of Add by using CallBase parameter. However, this might not work as your DbSet is not aware of list existence, you'll most likely have to configure it more heavily then.

On a final note, once your experiment crystallizes you should realize that configuring Add won't be necessary because you won't be checking whether blog was added via GetAllBlogs method but rather by verification on the mock itself:

mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-06-02 22:16

Did you try to use CallBase property?

mockSet.CallBase = true;
查看更多
登录 后发表回答