【问题标题】:How do you mock adding items to a repository or DbContext using moq?您如何使用 moq 模拟将项目添加到存储库或 DbContext?
【发布时间】:2020-10-21 09:39:31
【问题描述】:

我看到的将 moq 用于存储库的示例仅显示了如何模拟返回的内容。我有一个有点奇怪的要求:执行查询时,如果存在条件,则应该将某个项目添加到存储库中。我想知道如何在不查询数据库的情况下进行测试。我知道如何模拟存在的条件,但是如何设置模拟以便测试是否添加了某个项目?

【问题讨论】:

    标签: entity-framework unit-testing moq


    【解决方案1】:

    尝试在内存存储库中使用 fake 而不是 moq,例如所有实体的通用通用存储库:

    public interface IInMemoryRepository<T> where T : class
    {
        IQueryable<T> GetAll();
        void Create(T item);
        void Update(T item);
        T GetItem(Expression<Func<T, bool>> expression);
        void Delete(T item);
    }
    
    public class InMemoryRepository<T> : IInMemoryRepository<T> where T : class
    {
        private int _incrementer = 0;
        public Dictionary<int, T> List = new Dictionary<int, T>();
    
        public IQueryable<T> GetAll()
        {
            return List.Select(x => x.Value).AsQueryable();
        }
    
        public void Create(T item)
        {
            _incrementer++;
            item.GetType().GetProperties().First(p => p.Name == "Id").SetValue(item, _incrementer, null);
            List.Add(_incrementer, item);
        }
    
        public void Update(T item)
        {
            var key = (int)item.GetType().GetProperties().First(p => p.Name == "Id").GetValue(item, null);
            List[key] = item;
        }
    
        public T GetItem(Expression<Func<T, bool>> expression)
        {
            return List.Select(x => x.Value).SingleOrDefault(expression.Compile());
        }
    
        public void Delete(T item)
        {
            var key = (int)item.GetType().GetProperties().First(p => p.Name == "Id").GetValue(item, null);
            List.Remove(key);
        }
    }
    

    【讨论】:

      【解决方案2】:

      您不会模拟存储库;您将有一个备用存储库,该存储库将使用内存存储而不是数据库,然后使用 IoC 为测试/代码选择正确的存储库实现。

      【讨论】:

        【解决方案3】:

        This blog article might be of use,虽然自从我写这篇文章以来我的设计已经发生了一些变化,我真的需要更新它。我以一种能够模拟 DbContext 的方式使用了通用存储库模式。这允许“直到边缘”对数据访问层进行测试。

        【讨论】:

        • 感谢您的回答。我的主要问题是我将存储库注入到控制器中,而不是注入 DbContext 然后将其传递给存储库的构造函数。一旦我进行了更改并在内存中创建了一个假的 DbContext,就很容易测试场景。
        【解决方案4】:

        时代变了——自从Entity Framework 6 发布以来,模拟数据库上下文和数据集变得更加容易。 This article outlines the particulars.

        测试非查询场景

        using Microsoft.VisualStudio.TestTools.UnitTesting; 
        using Moq; 
        using System.Data.Entity; 
        
        namespace TestingDemo 
        { 
            [TestClass] 
            public class NonQueryTests 
            { 
                [TestMethod] 
                public void CreateBlog_saves_a_blog_via_context() 
                { 
                    var mockSet = new Mock<DbSet<Blog>>(); 
        
                    var mockContext = new Mock<BloggingContext>(); 
                    mockContext.Setup(m => m.Blogs).Returns(mockSet.Object); 
        
                    var service = new BlogService(mockContext.Object); 
                    service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet"); 
        
                    mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once()); 
                    mockContext.Verify(m => m.SaveChanges(), Times.Once()); 
                } 
            } 
        }
        

        测试查询场景 查询测试现在非常棒,因为您可以在代码中构建测试数据集,然后针对它们执行测试:

        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(0 => data.GetEnumerator()); 
        
                    var mockContext = new Mock<BloggingContext>(); 
                    mockContext.Setup(c => c.Blogs).Returns(mockSet.Object); 
        
                    var service = new BlogService(mockContext.Object); 
                    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); 
                } 
            } 
        }
        

        【讨论】:

        • 但这并不能回答 OP 的问题。他需要向底层存储添加一个实体(在测试期间可能是一个List(Of T)),然后验证它是否被正确添加。 Microsoft 示例未显示如何执行此操作。 BlogService.AddBlog() 返回Nothing 并且没有Blog 被添加到Context.Blogs
        • 操作方法如下:stackoverflow.com/a/47577162。基本上,您只需模拟 DbSet 方法。就我而言,我更喜欢使用.Returns(Of T) 而不是.Callback(Of T),所以我可以返回一个值:Instance.Setup(Function(DbSet) DbSet.Add(It.IsAny(Of T))).Returns(Function(Entity) Entities.Add(Entity) Return Entity End Function)
        【解决方案5】:

        您可以通过模拟DbSet.Add() 方法来做到这一点,如下所示:

        [Fact]
        public void CreateBlog_saves_a_blog_via_context()
        {
          var data = new List<Blog>
          {
            new Blog { Name = "BBB" },
            new Blog { Name = "ZZZ" },
            new Blog { Name = "AAA" },
          };
          
          var mockSet = new Mock<DbSet<Blog>>();
          mockSet.Setup(blogs => blogs.Add(It.IsAny<Blog>)).Returns<Blog>(blog =>
          {
            data.Add(blog);
            return blog;
          });
          
          var mockContext = new Mock<BloggingContext>();
          mockContext.Setup(m => m.Blogs).Returns(mockSet.Object);
          
          var service = new BlogService(mockContext.Object);
          var blog = service.AddBlog("_ADO.NET Blog", "http://blogs.msdn.com/adonet");
          var blogs = service.GetAllBlogs();
          
          mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
          mockContext.Verify(m => m.SaveChanges(), Times.Once());
          
          Assert.NotNull(blog)
          Assert.Equal(4, blogs.Count);
          Assert.Equal("AAA", blogs(1).Name);
          Assert.Equal("BBB", blogs(2).Name);
          Assert.Equal("ZZZ", blogs(3).Name);
        }
        

        本文改编自 here 的文档。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-09-08
          • 1970-01-01
          • 1970-01-01
          • 2014-05-26
          • 1970-01-01
          • 2018-06-28
          • 2015-09-29
          • 1970-01-01
          相关资源
          最近更新 更多