unit-testing - 带有Moq的单元测试EF存储库模式

标签 unit-testing entity-framework moq repository-pattern

我决定开始在我们的应用程序中编写单元测试。它使用带有存储库模式的 Entity Framework 。

现在,我想开始测试使用存储库的逻辑类。我在这里提供一个简单的例子。

我的类GenericRepository中的三个方法:

public class GenericRepository : IRepository
{
    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        var entityName = GetEntityName<TEntity>();
        return Context.CreateQuery<TEntity>(entityName);
    }
    private string GetEntityName<TEntity>() where TEntity : class
    {
        return typeof(TEntity).Name;
    }
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

一个简单的逻辑类,从日历表中按降序返回不同的年份(是的,我知道日历一词在我们的代码中拼写错误):
public class GetDistinctYearsFromCalendar
{
    private readonly IRepository _repository;

    public GetDistinctYearsFromCalendar()
    {
        _repository = new GenericRepository();
    }

    internal GetDistinctYearsFromCalendar(IRepository repository)
    {
        _repository = repository;
    }

    public int[] Get()
    {
        return _repository.Find<Calender_Tbl>(c => c.Year.HasValue).Select(c => c.Year.Value).Distinct().OrderBy(c => c).Reverse().ToArray();
    }
}

这是我的第一个测试:
[TestFixture]
public class GetDistinctYearsFromCalendarTest
{
    [Test]
    public void ReturnsDistinctDatesInCorrectOrder()
    {
        var repositoryMock = new Mock<IRepository>();

        repositoryMock.Setup(r => r.Find<Calender_Tbl>(c => c.Year.HasValue)).Returns(new List<Calender_Tbl>
        {
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2010, 1, 1),
                  Year = 2010
              },
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2010, 2, 1),
                  Year = 2010
              },
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2011, 1, 1),
                  Year = 2011
              }
        }.AsQueryable());

        var getDistinct = new GetDistinctYearsFromCalendar(repositoryMock.Object).Get();

        Assert.AreEqual(2, getDistinct.Count(), "Returns more years than distinct.");
        Assert.AreEqual(2011, getDistinct[0], "Incorrect order, latest years not first.");
        Assert.AreEqual(2010, getDistinct[1], "Wrong year.");


    }
}

一切正常。但这实际上不是我想要做的。由于必须在模拟对象上设置查找方法,因此我还需要知道如何在我的逻辑类中调用它。如果我想做TDD,我不想介意。我只想知道我的存储库应提供哪些Calendar实体。我想设置GetQuery方法。
像这样:
repositoryMock.Setup(r => r.GetQuery<Calender_Tbl>()).Returns(new List<Calender_Tbl>
{
  new Calender_Tbl
      {
          Date =
              new DateTime(2010, 1, 1),
          Year = 2010
      },
  new Calender_Tbl
      {
          Date =
              new DateTime(2010, 2, 1),
          Year = 2010
      },
  new Calender_Tbl
      {
          Date =
              new DateTime(2011, 1, 1),
          Year = 2011
      }
}.AsQueryable());

因此,当Find在GenericRepository类中内部调用GetQuery时,它应获取我在GetQuery中设置的正确Calendar实体。但这当然是行不通的。由于我尚未设置模拟对象的Find方法,因此我没有任何实体。

那么该怎么办?当然,我可能会使用Moles或其他可以模拟所有内容的框架,但我不想这样做。我可以在类设计或测试中解决问题吗?

如果我必须使用当前的解决方案,这不是世界末日,但是如果属性(property)年度变成不可为null的int,该怎么办?然后,当然,我将不得不更改逻辑类中的实现,但也必须更改测试。我想避免这种情况。

最佳答案

我可以看到两种方式:

public class MockRepository : IRepository
{
    private List<object> entities;
    public MockRepository(params object[] entitites)
    {
      this.entities = entities.ToList();
    }

    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        return this.entities.OfType<TEntity>().AsQueryable();
    }

    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

这是最简单,也是我的首选方式。最小起订量不是万能的;)

另外,如果您真的坚持使用Moq(我很受宠若惊,但是在这种情况下,这是不必要的,因为您可以对返回的实体进行基于状态的测试),您可以执行以下操作:
public class GenericRepository : IRepository
{
    public virtual IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        var entityName = GetEntityName<TEntity>();
        return Context.CreateQuery<TEntity>(entityName);
    }
    private string GetEntityName<TEntity>() where TEntity : class
    {
        return typeof(TEntity).Name;
    }
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

然后使用Moq覆盖GetQuery的行为:
var repository = new Mock<GenericRepository> { CallBase = true };

repository.Setup(x => x.GetQuery<Foo>()).Returns(theFoos.AsQueryable());

将会发生的是,Find方法将在GenericRepository类上执行,该类又将依次转换为GetQuery(已被Moq覆盖以提供固定的实体集)。

我明确设置了CallBase = true,以防万一您也使Find虚拟化,以便我们确保始终调用它。如果Find不是虚拟的,则从技术上讲不需要,因为它将始终在模拟所继承/模拟的实际类上被调用。

我会选择第一个选项,它更容易理解正在发生的事情,并且可以在单个特定测试的上下文之外重用(只需通过您需要的任何实体,它将对所有内容都有效)。

关于unit-testing - 带有Moq的单元测试EF存储库模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7281781/

相关文章:

javascript - 在 Webstorm 11 中使用 jasmine 运行/调试 node.js 测试用例

c# - .NET TransactionScope 和 MSDTC

moq - 新手尝试将 Moq 用于可枚举方法

c# - Moq.Mock<T> 使用 MOQ 将表达式设置为 Mock 会导致模拟设置不匹配

list - AssertJ:containsOnly和containsExactlyInAnyOrder有什么区别

database - playframework: -- 数据库/测试框架/缓存错误

c# - 无法在 NUnit 中加载程序集

c# - 函数导入(存储过程)是否需要 SaveChanges()?

c# - 如何检查我的实体对象是否延迟加载?

c# - Moq 回调方法未在 lambda 表达式中命中