如何模拟在using()块中实例化的对象

4

当从Entity Framework读取/写入上下文时,我经常看到建议尽可能缩短上下文的生命周期(每个工作单元一个上下文)。这是非常有道理的,但是如果每个方法都创建和释放此上下文,则该类如何编写单元测试?

让我们看一个简化的虚构示例代码片段:

public class Resource : IResource {
    public Item GetItem(string name) {
        using(var context = new DbContext()) {
            return context.Items.FirstOrDefault(item => item.Name == name);
        }
    }
}

如果我想对资源进行单元测试,我希望能够模拟DbContext,以便为我返回一些虚假数据。
我的常规方法是将DbContext作为我的类的属性,并像这样从外部注入它:
public class Resource : IResource {
    public DbContext Context { private get; set; }

    public Item GetItem(string name) {
        return this.Context.Items.FirstOrDefault(item => item.Name == name);
    }
}

这样,注入上下文的实例化类负责生命周期,我可以省略使用,当然也可以注入模拟上下文。
现在考虑到提供长时间存活的上下文可能是一个不好的主意,并且遵循“每个工作单元一个上下文”的原则,这个选项并不理想。
那么我有什么选择?一种想法是注入一个ContextFactory,根据需要创建和处理上下文,就像这样:
public class Resource : IResource {
    public DbContextFactory ContextFactory { private get; set; }

    public Item GetItem(string name) {
        using(var context = ContextFactory.CreateContext()) {
            return context.Items.FirstOrDefault(item => item.Name == name);
        }
    }
}

这是不是有意义,或者我走了一个完全错误的方向?


1
我相信你想要做的是一个集成测试。对于单元测试,使用 DbContextIRepository 应该被模拟。 - Dustin Kingen
你的ContextFactory实现是什么样子的?我遇到了和你一样的问题,我想要像你一样能够模拟上下文。你是如何模拟上下文工厂的?你会创建一个抽象类吗?然后在单元测试中设置它? - Mitch
2个回答

2
选项是使用方法注入:
   public Item GetItem(string name, DbContext context) {
        using(var context) {
            return context.Items.FirstOrDefault(item => item.Id == id);
        }
    }

Seemann在他的书《.Net中的依赖注入》中也使用了这种方法来注入依赖项,这些依赖项可以从调用到调用发生变化。

但我会使用ContextFactory


哦,对了!我应该在我的问题中写下这个。我也有这个想法,但是放弃了,因为我的类中可能有许多方法都使用相同的上下文。在每个方法中注入相同的上下文会过度,但感谢您提到了这个选项! - Jan
3
如果您打算在调用之间重复使用上下文,您应该从此方法中删除 using 语句。 - bryanbcook
1
我认为在任何情况下都应该删除using。如果上下文作为方法参数提供,则该方法不应干扰上下文的生命周期。只有实例化上下文的人应该对其生命周期负责。 - Jan
是的,我同意Jan的观点,处理上下文的方法并不是“纯粹”的,这种行为具有非明显的副作用。 - Anton Sizikov

1
我查看了Anton Sizikov提到的Mark Seemann的书,他也使用了一个工厂,称其为“短暂一次性”。这是一种有点不完美的抽象,因为工厂管理了可处理依赖项的生命周期,而非DI容器。尽管如此,这仍然比由于未处理的可处理对象导致内存泄漏或由已处理的共享依赖项引起的异常要好得多。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接