在Castle Windsor 3中,在单元测试中覆盖现有组件注册

31
我试图在自动化测试中使用Castle Windsor,步骤如下:
- 在每个测试中,Setup() 函数创建一个Windsor容器,并注册每个组件的默认实现。 - Test 函数通过方法 IWindsorContainer.Resolve<T> 访问组件,测试它们的行为。 - TearDown() 函数处理Windsor容器(以及任何创建的组件)的清理工作。
例如,我可能有15个测试,访问间接导致创建一个 IMediaPlayerProxyFactory 组件。 SetUp 函数注册了一个足够好的实现IMediaPlayerProxyFactory,因此我不必在这15个测试中都注册一遍。
然而,我现在正在编写一个名为Test_MediaPlayerProxyFactoryThrowsException的测试用例,确认我的系统是否优雅地处理来自IMediaPlayerProxyFactory组件的错误。在测试方法中,我已经创建了一个特殊的模拟实现,现在我想将其注入到框架中:
this.WindsorContainer.Register(
                                 Component.For<IMediaPlayerProxyFactory>()
                                          .Instance(mockMediaPlayerProxyFactory)
                              );

然而,Windsor抛出Castle.MicroKernel.ComponentRegistrationException异常,错误信息为"There is already a component with that name."

我是否有办法使我的mockMediaPlayerProxyFactory成为IMediaPlayerProxyFactory的默认实例,丢弃已经注册的组件?


根据文档,Castle Windsor 3允许覆盖注册,但我只找到了一个示例:

Container.Register(
    Classes.FromThisAssembly()
        .BasedOn<IEmptyService>()
        .WithService.Base()
        .ConfigureFor<EmptyServiceA>(c => c.IsDefault()));

ConfigureForBasedOnDescriptor 类的一个方法。在我的情况下,我没有使用 FromDescriptor 或者 BasedOnDescriptor

2个回答

72

要创建一个覆盖实例,你需要做两件事:

  1. 给它分配一个唯一的名称
  2. 调用IsDefault方法

所以要让这个例子正常工作:

this.WindsorContainer.Register(
   Component.For<IMediaPlayerProxyFactory>()
      .Instance(mockMediaPlayerProxyFactory)
      .IsDefault()
      .Named("OverridingFactory")
);

因为我计划在许多测试中使用这个覆盖模式,所以我创建了自己的扩展方法:
public static class TestWindsorExtensions
{
    public static ComponentRegistration<T> OverridesExistingRegistration<T>(
       this ComponentRegistration<T> componentRegistration
    ) where T : class
    {
        return componentRegistration
           .Named(Guid.NewGuid().ToString())
           .IsDefault();
    }
}

现在这个例子可以简化为:
this.WindsorContainer.Register(
   Component.For<IMediaPlayerProxyFactory>()
      .Instance(mockMediaPlayerProxyFactory)
      .OverridesExistingRegistration()
);

后续编辑

版本3.1引入了IsFallback方法。如果我将所有初始组件都注册为IsFallback,那么任何新的注册将自动覆盖这些初始注册。如果当时有这个功能,我本来会选择这条路线。

https://github.com/castleproject/Windsor/blob/master/docs/whats-new-3.1.md#fallback-components


实际实现中不需要命名和调用.IsDefault方法,除此之外非常好! - bevacqua
2
感谢您更新答案。 - Ufuk Hacıoğulları
1
IsFallback() 似乎无法与 Func 一起使用。例如,将 IsFallback() 添加到 container.Register(Component.For<Func<ICat>>().UsingFactoryMethod<Func<ICat>>(() => () => new Cat())); 中,如果稍后覆盖此注册,则不会防止重复键错误。但是,将 IsDefault().Named(Guid.New.ToString()) 添加到覆盖注册中可以解决问题。 - bugged87

1

不要在测试之间重复使用容器。相反,在TearDown()中将其设置为null,并为每个实际的测试重新初始化它。


抱歉,我可能没有表达清楚。我在TearDown()中处理了容器,然后在SetUp()中重新初始化它。我会修改我的介绍,以便更明确地表达这一点。 - Andrew Shepherd

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