【问题标题】:How to mock Entity Framework 6 Async methods?如何模拟 Entity Framework 6 异步方法?
【发布时间】:2014-01-30 12:20:07
【问题描述】:

我是嘲笑新手。我想模拟我的基础存储库,它依赖于 Entity Framework 6 DbContext 但我失败了。我在谷歌搜索了很多,但没有得到任何足够的结果。最后我在testing with async queries 找到了一个例子并尝试遵循,但它对我有用。

这是我的代码:

数据库上下文:

public class TimeSketchContext : DbContext
{
    public virtual DbSet<EmployeeSkill> EmployeeSkill { get; set; }
}

基础存储库:

public class BaseRepository<T> : IRepositoryBase<T> where T : class, IEntity, new()
{
    protected readonly DbContext InnerDbContext;
    protected DbSet<T> InnerDbSet;

    public BaseRepository(DbContext innerDbContext)
    {
        InnerDbContext = innerDbContext;
        InnerDbSet = InnerDbContext.Set<T>();
    }

    public virtual Task<T> FindAsync(long id)
    {
        return InnerDbSet.FirstOrDefaultAsync(x=>x.Id == id);
    }

}

测试:

    [Fact]
    public async Task DbTest()
    {
        var dummyData = GetEmployeeSkills();
        var mockSet = new Mock<DbSet<EmployeeSkill>>();

        mockSet.As<IDbAsyncEnumerable<EmployeeSkill>>()
            .Setup(x => x.GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<EmployeeSkill>(dummyData.GetEnumerator()));

        mockSet.As<IQueryable<EmployeeSkill>>()
            .Setup(x => x.Provider)
            .Returns(new TestDbAsyncQueryProvider<EmployeeSkill>(dummyData.Provider));

        mockSet.As<IQueryable<EmployeeSkill>>().Setup(m => m.Expression).Returns(dummyData.Expression);
        mockSet.As<IQueryable<EmployeeSkill>>().Setup(m => m.ElementType).Returns(dummyData.ElementType);
        mockSet.As<IQueryable<EmployeeSkill>>().Setup(m => m.GetEnumerator()).Returns(dummyData.GetEnumerator());

        var mockContext = new Mock<TimeSketchContext>();
        mockContext.Setup(c => c.EmployeeSkill).Returns(mockSet.Object);

        var baseRepository = new BaseRepository<EmployeeSkill>(mockContext.Object);

        var data = await baseRepository.FindAsync(1);

        Assert.NotEqual(null, data);

    }

    private EmployeeSkill GetEmployeeSkill()
    {
        return new EmployeeSkill
        {
            SkillDescription = "SkillDescription",
            SkillName = "SkillName",
            Id = 1
        };
    }

    private IQueryable<EmployeeSkill> GetEmployeeSkills()
    {
        return new List<EmployeeSkill>
        {
            GetEmployeeSkill(),
            GetEmployeeSkill(),
            GetEmployeeSkill(),
        }.AsQueryable();
    }

结果是:

Assert.NotEqual() 失败

我认为问题是

 public BaseRepository(DbContext innerDbContext)
 {
     InnerDbContext = innerDbContext;
     InnerDbSet = InnerDbContext.Set<T>();  <<<<<<<<<<<
 }

但不明白为什么以及如何解决这个问题。

我正在使用:

  • Visual Studio 2013 Ultimate
  • 起订量
  • xUnit

提前致谢。

【问题讨论】:

  • 对于未来的读者:如果您在各处注入 DbContext 并且您无法将其抽象为存储库/接口,并且您绝对需要“模拟”它,因为它是非虚拟的,您可以使用 Microsoft Fakes Assemblies 来实现这一点,因为它会做一些花哨的 IL 东西,让您为对象上的非虚拟方法提供替代实现。
  • 我强烈建议查看“Effort”(effort.codeplex.com),这是一个 EF 单元测试工具,它可以在内存中创建一个足够真实的 EF 数据库来进行测试。我们在模拟 EF 方面也遇到了很多麻烦,但归根结底,测试 EF 模拟是一场从苹果到橘子的惨败。 (例如,模拟测试使用 L2O 而不是 L2E。)
  • 它看起来是个不错的工具,稍后会检查。无论如何感谢@RJB
  • 对于 EF 6,我发现本文中的代码非常有用:msdn.microsoft.com/en-us/library/dn314429.aspx 我也遇到了模拟 AsNoTracking() 的问题,这个出色的答案解决了这个问题:stackoverflow.com/a/27087604/3507333
  • 该努力的 url 出现了一个谷歌警告,警告未来的危险程序,并建议不要继续。

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


【解决方案1】:

你是对的,问题出在你的InnerDbContext.Set&lt;T&gt;(); 声明中。

在当前版本的 EF (6.0.2) 中,DbContext.Set&lt;T&gt; method 不是 virtual,因此不能用 Moq 模拟。

因此,除非将 BaseRepository 的设计更改为不依赖于整个 DbContext 而是依赖于一个 DbSet&lt;T&gt;,否则您无法轻松通过测试:

比如:

public BaseRepository(DbSet<T> dbSet)
{
    InnerDbSet = dbSet;
}

然后你可以直接传入你的模拟 DbSet。

或者你可以为DbContext创建一个封装接口:

public interface IDbContext
{
    DbSet<T> Set<T>() where T : class;
}

public class TimeSketchContext : DbContext, IDbContext
{
    public virtual DbSet<EmployeeSkill> EmployeeSkill { get; set; }
}

然后在你的BaseRepository 中使用IDbContext

public class BaseRepository<T> : IRepositoryBase<T> where T : class, IEntity, new()
{
    protected readonly IDbContext InnerDbContext;
    protected DbSet<T> InnerDbSet;

    public BaseRepository(IDbContext innerDbContext)
    {
        InnerDbContext = innerDbContext;
        InnerDbSet = InnerDbContext.Set<T>();
    }

    public virtual Task<T> FindAsync(long id)
    {
        return InnerDbSet.FirstOrDefaultAsync(x => x.Id == id);
    }
}

最后,您只需要更改测试中的两行即可使其通过:

var mockContext = new Mock<IDbContext>();
mockContext.Setup(c => c.Set<EmployeeSkill>()).Returns(mockSet.Object);

【讨论】:

  • 投反对票,因为方法是FindAsync,但实现是FirstAndDefaultAsync
猜你喜欢
  • 2017-03-18
  • 2014-01-04
  • 2017-03-21
  • 2015-03-23
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多