In Castle Windsor 3, override an existing component registration in a unit test
Asked Answered
P

2

32

I am attempting to use Castle Windsor in my automated tests like so:

On every test:

  • The Setup() function creates a Windsor container, registering default implementations of each component
  • The Test function access the components via the method IWindsorContainer.Resolve<T>, and tests their behavior
  • The TearDown() function disposes of the Windsor container (and any created components)

For example, I might have 15 tests which accesses components which indirectly results in the creation of an IMediaPlayerProxyFactory component. The SetUp function registers a good-enough implementation IMediaPlayerProxyFactory, so I don't have the maintenance burden of registering this in each of the 15 tests.

However, I'm now writing a test Test_MediaPlayerProxyFactoryThrowsException, confirming my system elegantly handles an error from the IMediaPlayerProxyFactory component. In the test method I've created my special mock implementation, and now I want to inject it into the framework:

this.WindsorContainer.Register(
                                 Component.For<IMediaPlayerProxyFactory>()
                                          .Instance(mockMediaPlayerProxyFactory)
                              );

But Windsor throws a Castle.MicroKernel.ComponentRegistrationException, with the message "There is already a component with that name."

Is there any way that I can make my mockMediaPlayerProxyFactory be the default instance for the IMediaPlayerProxyFactory, discarding the component that's already registered?


According to the documentation, Castle Windsor 3 allows for registration overrides, but I could only find one example:
Container.Register(
    Classes.FromThisAssembly()
        .BasedOn<IEmptyService>()
        .WithService.Base()
        .ConfigureFor<EmptyServiceA>(c => c.IsDefault()));

ConfigureFor is a method of the BasedOnDescriptor class. In my case I'm not using the FromDescriptor or BasedOnDescriptor.

Pneumato answered 12/2, 2012 at 22:40 Comment(0)
P
73

There are two things that you have to do to create an overriding instance:

  1. Assign it a unique name
  2. Call the IsDefault method

So to get the example to work:

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

Because I plan to use this overriding patten in many tests, I've created my own extension method:

public static class TestWindsorExtensions
{
    public static ComponentRegistration<T> OverridesExistingRegistration<T>(
       this ComponentRegistration<T> componentRegistration
    ) where T : class
    {
        return componentRegistration
           .Named(Guid.NewGuid().ToString())
           .IsDefault();
    }
}

Now the example can be simplified to:

this.WindsorContainer.Register(
   Component.For<IMediaPlayerProxyFactory>()
      .Instance(mockMediaPlayerProxyFactory)
      .OverridesExistingRegistration()
);

Later Edit

Version 3.1 introduces the IsFallback method. If I register all my initial components with IsFallback, then any new registrations will automatically override these initial registrations. I would have gone down that path if the functionality was available at the time.

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

Pneumato answered 13/2, 2012 at 0:13 Comment(2)
naming and invoking .IsDefault method aren't required on the real implementation, other than that, very nice!Deroo
IsFallback() doesn't seem to work with Func. For example, adding IsFallback() to container.Register(Component.For<Func<ICat>>().UsingFactoryMethod<Func<ICat>>(() => () => new Cat())); does not prevent the duplicate key error if I override this registration later. However, adding IsDefault().Named(Guid.New.ToString()) to the overriding registration works.Thirzia
B
1

Don't reuse your container across tests. Instead, set it to null in the TearDown() and re-initialise it for each actual test.

Banky answered 12/2, 2012 at 23:3 Comment(1)
Sorry, I mustn't have been clear. I do dispose of the container in the TearDown(), and I re-initialise it in the SetUp(). I'll change my intro to try and make that more explicit.Pneumato

© 2022 - 2024 — McMap. All rights reserved.