Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple Injector, can't override existing registration

I am currently using Simple Injector for the first time. In my .NET project I am running test and mocking data returned from a web service and registering the object to the container like so

 _container.Register<IWebServiceOrder>(() => mock.Object, Lifestyle.Transient);

This works fine. But in my tests I want to test the behavior of the system on a second call to the web service that will contain updated data, so the mock object will need to be updated.

By default Simple Injector does not allow Overriding existing registrations but the official website states it is possible to change this behavior like below.

https://simpleinjector.readthedocs.org/en/latest/howto.html#override-existing-registrations

container.Options.AllowOverridingRegistrations = true;

Unfortunately i still get an error when i try to register the object a second time even with the above code.

The container can't be changed after the first call to GetInstance, GetAllInstances and Verify

Any ideas on why this is happening?

like image 932
user1843155 Avatar asked Oct 21 '14 10:10

user1843155


1 Answers

Replacing an existing registration after you already worked with the container hardly ever has the behavior you would expect (and this holds for all DI libraries) and that's the reason why the Simple Injector container is locked. This is described in more details here (as @qujck already pointed out).

First of all, if you are writing unit tests, you shouldn't use a container at all. Your unit tests should create the class under test themselves, or you extract this logic to a convenient factory method, such as:

private static MailNotifier CreateMailNotifier(
    IDeposit deposit = null, ISendMail mailSender = null, ILog logger = null)
{
  return new MailNotifier(
      deposit ?? Substitute.For<IDeposit>(),
      mailSender ?? Substitute.For<ISendMail>(),
      logger ?? Substitute.For<ILog>());
}

This factory method is a variation to the Test Data Builder pattern.

By making use of optional parameters, it allows the unit test to specify only the fake implementation it requires during testing:

public void Notify_WithValidUser_LogsAMessage()
{
    // Arrange
    var user = new User();

    var logger = new FakeLogger();

    MailNotifier sut = CreateMailNotifier(logger: logger);

    // Act
    sut.Notify(user);

    // Assert
    Assert.AreEqual(expected: 1, actual: logger.LoggedMessages.Count);
}

If you use a container, because creating the class under test by hand is too cumbersome, it indicates a problem in your class under test (most likely a Single Responsibility Principle violation). Prevent using tools to work around problems in your design; your code is speaking to you.

For integration tests however, it is much more usual to use the container, but in that case you should simply create a new container for each integration test. This way you can add or replace the IWebServiceOrder without any problem.

like image 74
Steven Avatar answered Sep 19 '22 02:09

Steven