【问题标题】:My Mocked DbContext writes to db in Unit Test using Moq我的 Mocked DbContext 使用 Moq 在单元测试中写入 db
【发布时间】:2014-10-10 14:43:41
【问题描述】:

我是模拟和使用 Moq 的新手。这是我第一次尝试模拟添加功能。我有 mocks 设置,它适用于读取函数,但是当我尝试对 add 方法进行单元测试时,它会将更改保存到 db。

如何模拟 add 方法?

我想我必须模拟具有 SaveChanges() 方法的 UnitOfWork 类。我想知道如何设置模拟来拦截对SaveChanges() 的调用而不是保存到数据库。

这是我到目前为止的模拟

[SetUp]
    public void SetUp()
    {
        addCount = 0;

        IEnumerable<Platform> platformList = new List<Platform>(){
            new Platform() { Id = 1, Name = "Unknown"},
            new Platform() { Id =2, Name = "Amazon"},
            new Platform() { Id = 3, Name = "Prime Pantry"}
        };
        var platformData = platformList.AsQueryable();

        var mockPlatformSet = new Mock<DbSet<Platform>>();
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Provider).Returns(platformData.Provider);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Expression).Returns(platformData.Expression);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.ElementType).Returns(platformData.ElementType);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.GetEnumerator()).Returns(platformData.GetEnumerator());
        mockPlatformSet.Setup(m => m.Add(It.IsAny<Platform>())).Callback(() => addCount++);

        var mockContext = new Mock<ApplicationDbContext>(){ CallBase = true };
        mockContext.Setup(m => m.Platforms).Returns(mockPlatformSet.Object);
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>()));
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>())).Callback(() => addCount++);


        unitOfWork = new UnitOfWork(mockContext.Object);
        platformRepo = new PlatformRepository(mockContext.Object);

        controller = new PlatformController(platformRepo, unitOfWork);
    }

添加 UnitOfWork 代码

public class UnitOfWork : IUnitOfWork 
{
    private readonly DbContext _context;
    private bool _isDisposed = false;

    public UnitOfWork(DbContext context)
    {
        _context = context;
    }

    public void SaveChanges()
    {
        _context.SaveChanges();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_isDisposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _isDisposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

}

更新问题

我正在尝试对 PlatformControllerCreate 方法进行单元测试。在这种方法中,我在存储库上调用Add 函数,然后调用UnitOfWorkSaveChanges 函数。我想验证我的 Platform 对象是否已添加到 DbSet,但“拦截”对 SaveChanges() 的调用以使其不写入 db。

我该怎么做?

【问题讨论】:

  • 当我尝试进行模拟时,我在上下文中模拟了集合。
  • 你能添加UnitOfWork构造函数的代码吗?
  • @elolos 我添加了 UnitOfWork 代码
  • 似乎没有为mockContext 设置SaveChanges 方法。不过,鉴于您注入了一个模拟上下文,我不明白为什么会有任何东西保存在数据库中。 UnitOfWork 中的代码看起来也不错。

标签: c# entity-framework unit-testing moq


【解决方案1】:

当我将我的mockContextCallBase 值设置为IUnitOfWorkfalse 时,这似乎解决了我的单元测试写入数据库的问题。

这行代码: mockContext.As&lt;IUnitOfWork&gt;().CallBase = false;

这是我的Setup函数的代码

[SetUp]
    public void SetUp()
    {
        addCount = 0;

        IEnumerable<Platform> platformList = new List<Platform>(){
            new Platform() { Id = 1, Name = "Unknown"},
            new Platform() { Id =2, Name = "Amazon"},
            new Platform() { Id = 3, Name = "Prime Pantry"}
        };
        var platformData = platformList.AsQueryable();

        var mockPlatformSet = new Mock<DbSet<Platform>>();
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Provider).Returns(platformData.Provider);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Expression).Returns(platformData.Expression);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.ElementType).Returns(platformData.ElementType);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.GetEnumerator()).Returns(platformData.GetEnumerator());
        mockPlatformSet.Setup(m => m.Add(It.IsAny<Platform>())).Callback(() => addCount++);

        var mockContext = new Mock<ApplicationDbContext>(){ CallBase = true };
        mockContext.Setup(m => m.Platforms).Returns(mockPlatformSet.Object);
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>()));
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>())).Callback(() => addCount++);
        mockContext.Setup(m => m.Set<Platform>()).Returns(mockPlatformSet.Object);
        mockContext.As<IUnitOfWork>().CallBase = false;

        unitOfWork = new UnitOfWork(mockContext.Object);
        platformRepo = new PlatformRepository(mockContext.Object);

        controller = new PlatformController(platformRepo, unitOfWork);
    }

【讨论】:

    【解决方案2】:

    我不确定这是否是您正在寻找的答案,但是使用工作单元抽象 EF DbContext 是一个糟糕的主意。原因是上下文已经一个工作单元实现。根据msdn中类的描述:

    表示工作单元和存储库模式的组合 并使您能够查询数据库并将更改组合在一起 然后将作为一个单元写回商店。

    一旦您删除了不必要的抽象,模拟上下文应该相当容易,特别是如果您使用最新版本的实体框架。

    【讨论】:

    • 感谢您的建议,但对于这个不会发生的项目。抽象太集成了,现在无法将其与时间线一起删除。
    【解决方案3】:

    您正在尝试在这里进行功能测试,因此拥有一个功能数据库是明智之举。

    EF 可以使用测试连接字符串在设置和拆卸方法中重新创建和销毁数据库。这将为您的测试提供一个真实的功能测试环境,以模拟真实环境。

    例如:

        [TestFixtureSetUp]
        public static void SetupFixture() //create database
        {
            using (var context = new XEntities())
            {
                context.Setup();
            }
        }
    
        [TestFixtureTearDown]
        public void TearDown() //drop database
        {
            using (var context = new XEntities())
            {
                context.Database.Delete();
            }
        }
    
        [SetUp]
        public void Setup() //Clear entities before each test so they are independent
        {
            using (var context = new XEntities())
            {
                foreach (var tableRow in context.Table)
                {
                    context.Table.Remove(tableRow);
                }
                context.SaveChanges();
            }
        }
    

    将您的测试项目中的连接字符串更改为指向“DbNameTest”,这样您就可以在测试中使用您的 XEntities 类,并且设置和添加要与之交互的测试数据是很清楚的。

    【讨论】:

    • 我认为他想写单元测试,而不是集成/功能测试。
    • 是的,但是当您测试数据库交互的功能时,您就是在进行功能测试。他可能认为他想要单元测试,但他实际上是在调用 add() 时测试功能。这样,他就可以访问由 EF 为该功能测试创建的测试数据库,而不是访问他的 prod 数据库。
    • 我认为大多数项目都需要两者。没有Assert 语句,但如果他只想单独测试控制器逻辑,那就是单元测试。
    • 我没有包含我的单元测试,但我确实有它们。我的目标是测试 Controller(MVC) 中的功能
    • @DFord 您是在测试 CALL add() 函数,还是在测试一个实体已添加到上下文中?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    • 2015-10-04
    • 2011-10-09
    • 1970-01-01
    相关资源
    最近更新 更多