【问题标题】:How do I go about unit testing with Entity Framework and Moq?如何使用 Entity Framework 和 Moq 进行单元测试?
【发布时间】:2018-01-15 10:36:45
【问题描述】:

我是 Moq 的新手,想将它用作数据的后备存储 - 但不接触实时数据库。

我的设置如下:

  • UnitOfWork 包含所有存储库,用于在整个应用程序中访问数据。
  • 存储库表示直接挂钩到 DbSet,由 DbContext 提供。
  • 一个 DbContext 包含所有 DbSet。

这是我目前的测试:

        // ARRANGE
        var user = new User()
        {
            FirstName = "Some",
            LastName = "Guy",
            EmailAddress = "some.guy@mockymoqmoq.com",
        };

        var mockSet = new MockDbSet<User>();
        var mockContext = new Mock<WebAPIDbContext>();

        mockContext.Setup(c => c.Set<User>()).Returns(mockSet.Object);

        // ACT
        using (var uow = UnitOfWork.Create(mockContext.Object))
        {
            uow.UserRepository.Add(user);
            uow.SaveChanges();
        }

        // ASSERT
        mockSet.Verify(u => u.Add(It.IsAny<User>()), Times.Once());

我的测试似乎成功了,因为它可以验证用户是否已添加到模拟 DbSet - 但我需要做的实际上是取回该数据并对其执行进一步的断言(这只是一个临时测试)。

请告知,测试框架正在我的头上。另外,如果它们更易于使用,我可以选择迁移到其他测试框架。

谢谢。

更新:这是我的工作代码。

单元测试

        // ARRANGE
        var user = new User()
        {
            FirstName = "Some",
            LastName = "Guy",
            EmailAddress = "some.guy@mockymoqmoq.com",
        };

        var mockSet = new MockDbSet<User>();
        var mockContext = new Mock<WebAPIDbContext>();

        mockContext.Setup(c => c.Set<User>()).Returns(mockSet.Object);

        // ACT
        using (var uow = UnitOfWork.Create(mockContext.Object))
        {
            uow.UserRepository.Add(user);
            uow.SaveChanges();
        }

        // ASSERT
        mockSet.Verify(u => u.Add(It.IsAny<User>()), Times.Once());

        // TODO: Further assertations can now take place by accessing mockSet.BackingStore.
    }

MockDbSet

class MockDbSet<TEntity> : Mock<DbSet<TEntity>> where TEntity : class
{
    public ICollection<TEntity> BackingStore { get; set; }

    public MockDbSet()
    {
        var queryable = (this.BackingStore ?? (this.BackingStore = new List<TEntity>())).AsQueryable();

        this.As<IQueryable<TEntity>>().Setup(e => e.Provider).Returns(queryable.Provider);
        this.As<IQueryable<TEntity>>().Setup(e => e.Expression).Returns(queryable.Expression);
        this.As<IQueryable<TEntity>>().Setup(e => e.ElementType).Returns(queryable.ElementType);
        this.As<IQueryable<TEntity>>().Setup(e => e.GetEnumerator()).Returns(() => queryable.GetEnumerator());

        // Mock the insertion of entities
        this.Setup(e => e.Add(It.IsAny<TEntity>())).Returns((TEntity entity) =>
        {
            this.BackingStore.Add(entity);

            return entity;
        });

        // TODO: Other DbSet members can be mocked, such as Remove().
    }
}

【问题讨论】:

  • 显示模拟数据库集的代码。您只需要创建一个集合作为后备存储并使用后备集合模拟枚举数据库集。
  • 很抱歉。我已经更新了我的帖子。 MockDbSet 未经测试,因为我不太确定它是如何组合在一起的。我怀疑“可查询”变量是否适用于 DbSet。我将如何通过连接到模拟 DbSet 来创建一个集合以充当后备存储?

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


【解决方案1】:

您只需要创建一个集合作为后备存储并使用后备集合模拟枚举数据库集

public class MockDbSet<TEntity> : Mock<DbSet<TEntity>> where TEntity : class {
    public MockDbSet(List<TEntity> dataSource = null) {
        var data = (dataSource ?? new List<TEntity>());
        var queryable = data.AsQueryable();

        this.As<IQueryable<TEntity>>().Setup(e => e.Provider).Returns(queryable.Provider);
        this.As<IQueryable<TEntity>>().Setup(e => e.Expression).Returns(queryable.Expression);
        this.As<IQueryable<TEntity>>().Setup(e => e.ElementType).Returns(queryable.ElementType);
        this.As<IQueryable<TEntity>>().Setup(e => e.GetEnumerator()).Returns(() => queryable.GetEnumerator());
        //Mocking the insertion of entities
        this.Setup(_ => _.Add(It.IsAny<TEntity>())).Returns((TEntity arg) => {
            data.Add(arg);
            return arg;
        });

        //...the same can be done for other members like Remove
    }
}

所以现在您可以使用列表来保存数据

// ARRANGE
var dataSource = new List<User>(); //<-- this will hold data
var user = new User()
{
    FirstName = "Some",
    LastName = "Guy",
    EmailAddress = "some.guy@mockymoqmoq.com",
};

var mockSet = new MockDbSet<User>(dataSource);
var mockContext = new Mock<WebAPIDbContext>();

mockContext.Setup(c => c.Set<User>()).Returns(mockSet.Object);

// ACT
using (var uow = UnitOfWork.Create(mockContext.Object))
{
    uow.UserRepository.Add(user);
    uow.SaveChanges();


    // ASSERT
    mockSet.Verify(u => u.Add(It.IsAny<User>()), Times.Once());
    Assert.IsTrue(dataSource.Contains(user)); //<-- shows mock actually added item
    Assert.IsTrue(uow.UserRepository.Any(u => u == user)); //<-- show you can actually query mock DbSet
}

【讨论】:

  • 感谢这个有用的答案,当我尝试在 .Net 5 上使用最新的实体框架来实现它时,我发现了一个语法错误,需要强制转换将实体添加到数据库集中的方法像这样的 EntityEntry://Mocking the insertion of entities Setup(_ =&gt; _.Add(It.IsAny&lt;TEntity&gt;())).Returns((TEntity arg) =&gt; { data.Add(arg); return arg.As&lt;EntityEntry&lt;TEntity&gt;&gt;(); });
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-24
  • 2013-06-26
  • 1970-01-01
  • 2011-01-30
  • 1970-01-01
相关资源
最近更新 更多