删除Simple Injector

时间:2016-07-28 19:27:32

标签: c# dependency-injection simple-injector

好的,我知道这听起来像一个奇怪的请求,但这是我的问题。我想为我的WCF服务编写一些集成测试;我有一些关键路径,我想确保正常行为。一个测试是确保在关键位置抛出正确的异常,并且它们正确地向上传播,而不会在错误的位置被截获。

所以要做到这一点,我将覆盖一个模拟对象的现有注册,该模拟对象将抛出我想要在我想要它抛出的位置进行测试的异常。那部分工作正常。

接下来,我想解析我的命令处理程序(被测系统),调用handle方法,并断言正确的异常发生。

问题在于,当我解析我的命令处理程序时,实际上我的命令处理程序一直在底部返回一个loooong装饰器链。在这个链的最顶端坐着一个装饰器,它是我的全局异常处理程序。顶部的这个异常处理装饰器需要取消注册,因为它阻止我能够断言抛出异常。我的容器引导程序非常复杂,所以我绝对不想在我的测试项目中重新创建它的副本而不是这个装饰器。

如果只是标准注册,我可以简单地使用重新抛出异常的模拟异常处理程序覆盖注册。据我所知,似乎无法覆盖装饰者的注册。无论如何,我宁愿不去那条路。它只是通过额外的模拟使测试复杂化。如果我可以取消注册装饰器会更好。

如果无法取消注册装饰器,那么下一个最佳解决方案是什么?向我的引导程序添加选项标志以启用/禁用某些注册?

感谢。

2 个答案:

答案 0 :(得分:3)

据我所知,无法删除任何注册。

通过单元测试,您通常根本不会使用容器。由于您正在执行集成测试,因此使用容器确实是必须的。

我可以想到两种方法来做你想做的事。

第一种是将一些选项标志传递给你的引导程序,它在生产和测试环境之间交换。

第二个是考虑你的测试方法。从您的问题来看,您的ICommandHandler链中的某个部分似乎应该抛出异常。

我认为使用普通单元测试而不是集成测试来测试非常简单。在这种情况下,您不会使用容器,而是手动创建链。

你的commandhandler的单元测试就像下面这样简单:

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void CommandHandlerThrowsCorrectException()
{
    var handler = new Decorator1(new Decorator2(new MyHandler()));

    handler.Handle(new MyCommand);
}

您可以使用其他集成测试来检查传递给WCF服务的命令是否导致构建和处理正确的ICommandHandler链。

我通常使用以下测试设置:

  • 使用单元测试来测试您手头的所有案例,而不使用容器=>这意味着应用程序中每个组件至少进行一次单元测试
  • 使用单元测试来测试每个单独的装饰器,而不使用容器=>在您的情况下GenericExceptionCommandHandlerDecorator处理例外
  • 也是如此
  • 使用单元测试来测试对容器进行的注册是否正确以及装饰器是否以正确的顺序应用,当然是使用容器。如果您使用内置verification of the made registrations,使用container.Verify()
  • ,则此作业的一部分已由容器完成
  • 尽可能少地使用集成测试,只是为了测试应用程序是否正常工作和流动。因为每个组件和装饰器都经过单元测试,所以在集成测试中测试应用程序行为的需求要少得多。总会出现您希望模仿用户与应用程序交互的场景,但它应该很少见,并且大多数都是由单元测试覆盖。

答案 1 :(得分:3)

在Simple Injector中删除注册是不可能的。您可以replace an existing registration,但在处理装饰器时该方法不起作用。通过向ExpressionBuilt事件添加委托,在内部的Simple Injector中添加装饰器。由于注册代表无处存储,因此目前在技术上不可能注销'装饰师注册。

解决这个问题的方法是简单地注册那个装饰器。这可能听起来很愚蠢,但这是我一直使用的做法,即使是其他容器也是如此。

例如,你可以做的是将注册的公共部分提取到一个单独的方法,让我们称之为BuildUp。此方法缺少与使用它的不同应用程序不同的注册。在您的情况下,您至少有2个应用程序&#39 ;;真正的应用程序和集成测试项目。两个项目都可以调用BuildUp并在调用BuildUp之前或之后添加额外的注册。例如:

var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();

container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(InnerMostCommandHandlerDecorator<>));

CompositionRoot.BuildUp(container);

container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(OuterMostCommandHandlerDecorator<>));

这种方法似乎在您的情况下非常有效,因为您想要添加最外层的&#39;装饰。此外,让BuildUp离开&#39;在您的注册中,通常很容易看到某些应用程序忘记填写空白,因为您可以通过调用container.Verify()让Simple Injector快速失败。

我们将配置对象传递给BuildUp方法的另一种常用方法。此配置对象可以包含用于在调用者需要时进行正确注册的必要信息。例如,这样的配置对象可以有一个简单的布尔标志:

public static void Build(Container container, ApplicationConfig config) {

    // Lot's of registrations

    if (config.AddGlobalExceptionHandler) {
        container.RegisterDecorator(typeof(ICommandHandler<>),
            typeof(GlobalExceptionHandlerCommandHandlerDecorator<>));
    }
}

将配置对象传递到BuildUp方法也是将BuildUp方法与配置系统分离的好方法。这使您可以在集成测试期间更轻松地调用BuildUp,而无需在测试项目中获得完整配置文件的副本。

您可以在配置对象中使用完整的装饰器列表,而不是使用标志属性,让BuildUp方法迭代它并注册它们。这允许调用者在注册之前从列表中删除或添加装饰器:

var config = new ApplicationConfig();

// Remove decorator
config.CommandHandlerDecorators.Remove(
    typeof(AuthorizationCommandHandlerDecorator<>));

// Add decorator after another decorator
config.CommandHandlerDecorators.Insert(
    index: 1 + config.CommandHandlerDecorators.IndexOf(
        typeof(TransactionCommandHandlerDecorator<>)),
    item: typeof(DeadlockRetryCommandHandlerDecorator<>));

// Add an outer most decorator
config.CommandHandlerDecorators.Add(
    typeof(TestPerformanceProfilingCommandHandlerDecorator<>));

CompositionRoot.BuildUp(container, config);


public static void BuildUp(Container container, ApplicationConfig config) {

    // Lot's of registrations here.

    config.CommandHandlerDecorators.ForEach(type =>
        container.RegisterDecorator(typeof(ICommandHandler<>), type));
}

我过去非常成功地使用过这三种方法。选择哪个选项取决于您的需求。