My Moked DbContext在单元测试中使用Moq写入数据库

本文关键字:Moq 数据库 Moked DbContext 单元测试 My | 更新日期: 2023-09-27 18:26:08

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

如何模拟add方法?

我想我必须Mock我的UnitOfWork类,它有SaveChanges()方法。我想知道如何设置mock来拦截对SaveChanges()的调用,而不是保存到db。

这是迄今为止我拥有的模型

[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()的调用,使其不写入数据库。

我该怎么做?

My Moked DbContext在单元测试中使用Moq写入数据库

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

这行代码:mockContext.As<IUnitOfWork>().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);
    }

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

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

一旦删除了不必要的抽象,模拟上下文应该相当容易,尤其是在使用最新版本的实体框架的情况下。

您正在尝试在这里进行功能测试,因此最好拥有一个功能数据库。

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类,并且可以设置和添加要交互的测试数据。