c# – 如何对使用DbContext和NSubstitute的存储库进行单元测试?

我有一个解决方案,其中我有一个数据项目,其中包含从现有数据库生成的EF6 .edmx文件.我将实体拆分为一个单独的实体项目,并有一个引用它们的存储库项目.

我添加了一个带有一些常用方法的BaseRepository,并希望对它进行单元测试.班级的顶端看起来像这样……

public class BaseRepository<T> : BaseRepositoryInterface<T> where T : class {
  private readonly MyEntities _ctx;
  private readonly DbSet<T> _dbSet;

  public BaseRepository(MyEntities ctx) {
    _ctx = ctx;
    _dbSet = _ctx.Set<T>();
  }

  public IEnumerable<T> GetAll() {
    return _dbSet;
  }

  //...
}

按照我在https://*.com/a/21074664/706346找到的代码,我尝试了以下内容……

[TestMethod]
public void BaseRepository_GetAll() {
  IDbSet<Patient> mockDbSet = Substitute.For<IDbSet<Patient>>();
  mockDbSet.Provider.Returns(GetPatients().Provider);
  mockDbSet.Expression.Returns(GetPatients().Expression);
  mockDbSet.ElementType.Returns(GetPatients().ElementType);
  mockDbSet.GetEnumerator().Returns(GetPatients().GetEnumerator());
  MyEntities mockContext = Substitute.For<MyEntities>();
  mockContext.Patients.Returns(mockDbSet);

  BaseRepositoryInterface<Patient> patientsRepository 
                          = new BaseRepository<Patient>(mockContext);
  List<Patient> patients = patientsRepository.GetAll().ToList();
  Assert.AreEqual(GetPatients().Count(), patients.Count);
}

private IQueryable<Patient> GetPatients() {
  return new List<Patient> {
    new Patient {
      ID = 1,
      FirstName = "Fred",
      Surname = "Ferret"
    }
  }
    .AsQueryable();
}

请注意,我更改了上下文TT文件以使用IDbSet,正如Stuart Clement在2015年12月4日22:41的评论中所建议的那样

但是,当我运行此测试时,它会给出一个空引用异常,因为基本存储库构造函数中设置_dbSet的行会将其保留为null …

_dbSet = _ctx.Set<T>();

当我设置我的模拟上下文时,我猜我需要添加另一行,但我不确定是什么.我认为上面的代码应该足以填充DbSet.

有谁能解释我错过了什么或做错了什么?

解决方法:

好吧,我试图按照我在问题中展示的方式让自己疯狂,我遇到了Effort,这是专为此任务而设计的,并且遵循了this tutorial,这让我开始了.我的代码有一些问题,我将在下面解释.

简而言之,我所做的是……

*)在测试项目中安装Effort.EF6.我一开始犯了一个错误并安装了Effort(没有EF6位),并且遇到了各种各样的问题.如果您使用的是EF6(或我认为的EF5),请确保安装此版本.

*)修改了MyModel.Context.tt文件以包含一个带有DbConnection的额外构造函数… public MyEntities(DbConnection connection):base(connection,true){}

*)将连接字符串添加到测试项目的App.Config文件中.我从数据项目中逐字复制了这个.

*)为测试类添加了初始化方法以设置上下文…

private MyEntities _ctx;
private BaseRepository<Patient> _patientsRepository;
private List<Patient> _patients;

[TestInitialize]
public void Initialize() {
  string connStr = ConfigurationManager.ConnectionStrings["MyEntities"].ConnectionString;
  DbConnection connection = EntityConnectionFactory.CreateTransient(connStr);
  _ctx = new MyEntities(connection);
  _patientsRepository = new BaseRepository<Patient>(_ctx);
  _patients = GetPatients();
}

重要 – 在链接的文章中,他使用DbConnectionFactory.CreateTransient(),当我尝试运行测试时,它给出了异常.似乎这是针对Code First的,因为我正在使用Model First,所以我必须将其更改为使用EntityConnectionFactory.CreateTransient().

*)实际测试相当简单.我添加了一些辅助方法来尝试整理它,并使其更具可重用性.在我完成之前,我可能会再进行几轮重构,但这样做有效,并且现在足够干净……

[TestMethod]
public void BaseRepository_Update() {
  AddAllPatients();
  Assert.AreEqual(_patients.Count, _patientsRepository.GetAll().Count());
}

#region Helper methods

private List<Patient> GetPatients() {
  return Enumerable.Range(1, 10).Select(CreatePatient).ToList();
}

private static Patient CreatePatient(int id) {
  return new Patient {
    ID = id,
    FirstName = "FirstName_" + id,
    Surname = "Surname_" + id,
    Address1 = "Address1_" + id,
    City = "City_" + id,
    Postcode = "PC_" + id,
    Telephone = "Telephone_" + id
  };
}

private void AddAllPatients() {
  _patients.ForEach(p => _patientsRepository.Update(p));
}

#endregion

这里需要思想转变的一点就是使用Effort,与其他模拟不同,你不会告诉模拟框架为特定参数返回什么.相反,你必须把Effort想象成一个真正的数据库,尽管它是一个临时存储器.因此,我在初始化中设置了一个模拟患者列表,将它们添加到数据库中,然后才进行实际测试.

希望这有助于某人.事实证明,这比我最初尝试的方式要容易得多.

上一篇:c#-从收到的调用中提取参数并对其进行断言


下一篇:AD9833资料收集