2

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?

Liam Flanagan
  • 408
  • 5
  • 11
  • You can try the `CallBase = true` on your mock. `var mockSet = new Mock> { CallBase = true };` so moq will call the DbSet's Add method. – nemesv Jun 17 '14 at 08:27
  • Thanks for your response, however this doesn't seem to have helped; do I need to setup up some kind of link for the add function to add to the underlying list? – Liam Flanagan Jun 17 '14 at 08:43

1 Answers1

3

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.

  1. Create an interface to wrap the EF functionality. Mock this interface. This solution limits the number of methods to mock
  2. 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).
Community
  • 1
  • 1
JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • Callbacks were the answer to the question I asked, which is perfect for basic unit testing; however the extra information about in memory database implementations was incredibly useful. I'm now looking into using [Effort](http://effort.codeplex.com/) for more complex integration tests. – Liam Flanagan Jun 17 '14 at 23:54
  • Yes, using an in-memory DB makes testing easier: you will not have to write mocks for EF contexts (In fact there are mocks very hard to implement, i.e. mocking a query with several `Include()`, or updating an entity with related entities: with in-memory that's really easy). Besides, the tests will run as fast as unit tests with mocks, because they don't hit a real DB or need to read/write on disk. (I still prefer decoupling the EF with interfaces, and mocking those interface, because I use a different architecture, and I blindly rely on the real interface implementation) – JotaBe Jun 18 '14 at 09:25