在进行单元测试时,您根本不应该使用容器。只需通过调用其构造函数并为其提供适当的模拟对象来创建被测类。
过去对我有很大帮助的一种模式是使用简单的特定于测试类的工厂方法。该方法集中了被测类的创建,并最大限度地减少了当被测类的依赖关系发生变化时需要进行的更改量。这就是这种工厂方法的样子:
private ClassUnderTest CreateValidClassUnderTest(params object[] dependencies)
{
return new ClassUnderTest(
dependencies.OfType<ILogger>().SingleOrDefault() ?? new FakeLogger(),
dependencies.OfType<IMailSender>().SingleOrDefault() ?? new FakeMailer(),
dependencies.OfType<IEventPublisher>().SingleOrDefault() ?? new FakePublisher());
}
对于集成测试,更常见的是使用容器,并交换容器的一些依赖项。尽管如此,这些集成测试不会使用您在 application_start 中创建的容器,但在这种情况下,每个集成测试很可能都有自己的新容器实例,因为每个测试都应该独立运行。即使您确实使用了 application_start 中的单个容器,您的集成测试也是从一个单独的项目中运行的,并且不会干扰您正在运行的应用程序。
尽管每个集成测试都应该有自己的容器实例(如果有的话),但您仍然希望尽可能多地重用容器配置代码。这可以通过将此代码提取到一个方法中来完成,该方法要么在调用时返回一个新的配置容器实例,要么配置一个提供的容器实例(并且不返回任何内容)。此方法通常应执行不完整的配置,调用者(您的测试或全局 asax)应添加缺少的配置。
提取此代码:允许您拥有多个部分共享相同配置的终端应用程序;允许您在集成测试中验证容器;并允许您添加需要由集成测试模拟的服务。
为了让生活更轻松,Simple Injector 允许您将现有注册替换为新注册(例如模拟的注册)。您可以按如下方式启用此功能:
container.Options.AllowOverridingRegistrations = true;
但是要小心这个!此选项可以隐藏您意外覆盖注册的事实。根据我的经验,在大多数情况下,构建一个不完整的容器并在之后添加丢失的注册而不是覆盖它们要好得多。或者,如果您决定覆盖,请在最后一刻启用该功能,以防止任何意外的错误配置。