好的,我知道这听起来像一个奇怪的请求,但这是我的问题。我想为我的WCF服务编写一些集成测试;我有一些关键路径,我想确保正常行为。一个测试是确保在关键位置抛出正确的异常,并且它们正确地向上传播,而不会在错误的位置被截获。
所以要做到这一点,我将覆盖一个模拟对象的现有注册,该模拟对象将抛出我想要在我想要它抛出的位置进行测试的异常。那部分工作正常。
接下来,我想解析我的命令处理程序(被测系统),调用handle方法,并断言正确的异常发生。
问题在于,当我解析我的命令处理程序时,实际上我的命令处理程序一直在底部返回一个loooong装饰器链。在这个链的最顶端坐着一个装饰器,它是我的全局异常处理程序。顶部的这个异常处理装饰器需要取消注册,因为它阻止我能够断言抛出异常。我的容器引导程序非常复杂,所以我绝对不想在我的测试项目中重新创建它的副本而不是这个装饰器。
如果只是标准注册,我可以简单地使用重新抛出异常的模拟异常处理程序覆盖注册。据我所知,似乎无法覆盖装饰者的注册。无论如何,我宁愿不去那条路。它只是通过额外的模拟使测试复杂化。如果我可以取消注册装饰器会更好。
如果无法取消注册装饰器,那么下一个最佳解决方案是什么?向我的引导程序添加选项标志以启用/禁用某些注册?
感谢。
答案 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
处理例外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));
}
我过去非常成功地使用过这三种方法。选择哪个选项取决于您的需求。