【问题标题】:Effort- FirstOrDefault returns null when Faking DatabaseEffort-FirstOrDefault 在伪造数据库时返回 null
【发布时间】:2016-08-14 04:47:19
【问题描述】:

我正在尝试为我的项目创建一些单元测试,经过大量挖掘后,我发现了 Effort,这个想法很棒,它模拟数据库而不是处理伪造 DBContext,顺便说一句,这真的很难得到使用复杂架构时是正确的。

但是,在我专门将用户的电子邮件添加到 Effort 创建的内存数据库中后,我试图获取用户的电子邮件,这里是代码

MyContext contextx = new MyContext(Effort.DbConnectionFactory.CreateTransient());

var client = new Client
{
    ClientId = 2,
    PersonId = 3,
    Person = new Person
    {
        PersonId = 3,
        EMail = "xxxxx@gmail.com"
    }
};
contextx.Client.Add(client); //<-- client got added, I checked it and is there

var email = contextx.Client.Select(c => c.Person.EMail).FirstOrDefault(); 

在上面的最后一行中,我无法返回电子邮件 xxxx@gmail.com,而是始终返回 null。

有什么想法吗?

【问题讨论】:

  • contextx.SaveChanges();
  • @Fabio 是的,解决了它

标签: c# entity-framework unit-testing mocking effort


【解决方案1】:

回答您的直接问题

对于你提出的具体问题,我建议两点:

  1. 看看contextx.Client.ToArray(),看看你在这个集合中有多少成员。可能Client 集合实际上是空的,在这种情况下,您确实会得到空值。或者,可能是 Client 集合中的第一个元素的 EMail 为空值。

  2. 如果在 DbContext 上查询 Client 集合之前调用 contextx.SaveChanges(),行为会发生什么变化?我很好奇调用SaveChanges 是否会导致新插入的值存在于集合中。这确实不是必需的,但 Effort 和 DbContext 之间可能存在一些奇怪的交互。

编辑: SaveChanges() 原来是答案。

一般测试建议

由于您使用“单元测试”标签标记了这个问题,我将根据我作为单元测试从业者和教练的十年来提供一些通用的单元测试建议。 单元测试是关于隔离测试应用程序的各个小部分。通常这意味着单元测试一次只与几个类交互。这也意味着单元测试不应依赖于外部库或依赖项(例如数据库)。相反,集成测试同时测试系统的更多部分,并且可能对数据库等事物具有外部依赖性。

虽然这看起来像是对术语的争论,但这些术语对于将测试的实际意图传达给团队的其他成员很重要。

在这种情况下,要么您真的想要对恰好依赖于 DbContext 的某些功能进行单元测试,要么您正在尝试测试您的数据访问层。如果您尝试编写一个直接依赖于 DbContext 的独立单元测试,那么您需要打破对 DbContext 的依赖。我将在下面的Breaking the Dependency on DbContext 中对此进行解释。否则,您实际上是在尝试集成测试您的 DbContext,包括您的实体是如何映射的。在这种情况下,我总是发现最好隔离这些测试并使用真实的(本地)数据库。您可能希望使用与您在生产中使用的相同种类的本地安装数据库。通常,SqlExpress 工作得很好。将您的测试指向数据库的一个实例,测试可以完全丢弃该实例。让您的测试在运行每个测试之前删除任何现有数据。然后,他们可以设置所需的任何数据,而不必担心现有数据会发生冲突。

打破对 DbContext 的依赖

那么,当你的业务逻辑依赖于访问DbContext 时,你如何编写好的单元测试呢? 你没有。

在使用 Entity Framework 进行数据持久性的应用程序中,我确保对 DbContext 的访问包含在单独的数据访问项目中。通常,我将创建实现存储库模式的类,并且允许这些类依赖于DbContext。因此,在这种情况下,我将创建一个实现IClientRepository 接口的ClientRepository。界面看起来像这样:

public interface IClientRepository {

    Client GetClientByEMail(string email);

}

然后,任何需要访问该方法的类都可以使用基本的存根/模拟/任何东西进行单元测试。无需担心嘲笑DbContext。包含您的数据访问层,您可以使用真实数据库对其进行彻底测试。有关如何测试数据访问层的一些建议,请参见上文。

作为一个额外的好处,这个接口的实现定义了在一个统一的地方通过电子邮件地址找到Client 的含义。 IClientRepository 界面让您可以快速回答“我们如何查询系统中的Client 实体?”的问题

依赖DbContext 与允许域类依赖连接字符串并在任何地方都有ADO.Net 代码的测试问题的规模大致相同。这意味着您必须创建一个包含真实数据的真实数据存储(即使使用假数据库)。但是,如果您在特定的数据访问程序集中包含对 DbContext 的访问权限,您会发现您的单元测试更容易编写。

就项目组织而言,我通常只允许我的数据访问项目引用实体框架。我将有一个单独的核心项目,在其中定义实体。我还将在 Core 项目中定义数据访问接口。然后,将具体的接口实现放入数据访问项目中。然后,您的解决方案中的大多数项目可以简单地依赖 Core 项目,而只有顶级可执行文件或 Web 项目真正需要依赖数据访问项目。

【讨论】:

  • 调用 SaveChanges 修复了它,关于存储库模式,我已经在其他项目中沿着这条路线走,它已经到了我的存储库类中有太多函数用于最少东西的地步,因为在这种情况下,由于这个问题,我特别不想将存储库模式与 UnitOfWork 一起使用,对于查询的每个变体,都需要创建一个新函数,IMO 按照您建议的方式进行操作太麻烦了,但是谢谢.
  • 我明白了。我们都有自己的喜好。为了其他人阅读本文,我将详细说明我的推理。我更喜欢单独测试数据访问,因为我知道它可以工作。正是由于你提出的原因,我更喜欢这种孤立。我想要测试每个查询以确保一切正常。在 DAL 之外,我希望能够在不设置真实实体的情况下轻松删除这些结果。
  • @Fabio 你在说什么我没有回答这个问题?我的答案的顶部是contextx.SaveChanges()。请参见要点 #2。原始海报甚至对此答案的第一条评论也有评论。请在投反对票之前仔细阅读回复。
  • 从要点 2:我很好奇调用 SaveChanges 是否会导致新插入的值存在于集合中。 这确实不是必需的,但 Effort 和 DbContext 之间可能存在一些奇怪的交互。我很抱歉,但这似乎更像是评论而不是答案。关于打破对数据库层的依赖,我百分百支持你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多