delphiunit-testingspring4ddelphi-mocks

spring4d unregister interface type (spring4d, dunitx, delphi-mocks)


I am writing one of my first unit-tests using spring4d, dunitx and delphi-mocks. (spring4d Release 1.1 - 12.09.2014)

In my testing application, I auto-wire-inject an interface to my System under test class (sut):

IMyInterface = interface [{yes, a GUID here}]
  function GetSomething1: TSomething1;
  function GetSomething2: TSomething2;
end;

TMyClass = class
private
    [Inject]
    FMyInterface: IMyInterface;
    // ...
end;

Now when I am using unit-tests with mocking, I use the following (very simplyfied) code:

TMyTest = class
private
    [Test]
    procedure Test1;

    [Test]
    procedure Test2;
    // ...
end;

procedure TMyTest.Test1;
var
    aSut: TMyClass;
    aIntfMock: TMock<IMyInterface>;
    aSomething1: TSomething1;
begin
    // Arrange
    GlobalContainer.RegisterType<TMyClass>;

    aIntfMock := TMock<IMyInterface>.Create;
    try
        GlobalContainer.RegisterType<IMyInterface>.DelegateTo(
            function: IMyInterface
            begin
                Result := aIntfMock;
            end)
            .AsDefault;

        GlobalContainer.Build;

        aSomething1 := TSomething1.Create;
        try
            aIntfMock.Setup.WillReturn(TValue.From<TSomething1>(aSomething1)).When.GetSomething1;

            aSut := GlobalContainer.Resolve<TMyClass>;
            try
                // Act
                aSut.DoSomething;
            finally
                 FreeAndNil(aSut);
            end;

            // Assert
            // ...
        finally
            FreeAndNil(aSomething1);
        end;
    finally
        aIntfMock.Free; 
    end;
end;

procedure TMyTest.Test2;
var
    aSut: TMyClass;
    aIntfMock: TMock<IMyInterface>;
    aSomething2: TSomething2;
begin
    // Arrange
    GlobalContainer.RegisterType<TMyClass>;

    aIntfMock := TMock<IMyInterface>.Create;
    try
        GlobalContainer.RegisterType<IMyInterface>.DelegateTo(
            function: IMyInterface
            begin
                Result := aIntfMock;
            end)
            .AsDefault;


        GlobalContainer.Build;   // <---- ERegistrationException here

        aSomething2 := TSomething1.Create;
        try
            aIntfMock.Setup.WillReturn(TValue.From<TSomething2>(aSomething2)).When.GetSomething2;

            aSut := GlobalContainer.Resolve<TMyClass>;
            try
                // Act
                aSut.DoSomething;
            finally
                 FreeAndNil(aSut);
            end;

            // Assert
            // ...
        finally
            FreeAndNil(aSomething2);
        end;
    finally
        aIntfMock.Free; 
    end;
end;

The first test method (Test1) runs fine... but in the second test method(Test2), in the line with GlobalContainer.Build spring4d raises an exception: ERegistrationException('Duplicate service name found: IMyInterface_u.IMyInterface@IMyInterface_u.IMyInterface').

Is there a possibility to unregister aIntfMock, so I can register a new one for every other test-routine?

[Edit] So the solution would be:

TMyTest = class
private
    FTestContainer: TContainer;
    FIntfMock: TMock<IMyInterface>;
    FSut: TMyClass;

    [Setup]
    procedure Setup;
    [TearDown]
    procedure TearDown;
    [Test]
    procedure Test1;

    [Test]
    procedure Test2;
    // ...
end;

//---------------------

procedure TMyTest.Setup;
begin
    FTestContainer := TContainer.Create;
    FTestContainer.RegisterType<TMyClass>;

    FIntfMock := TMock<IMyInterface>.Create;    

    FTestContainer.RegisterType<IMyInterface>.DelegateTo(
        function: IMyInterface
        begin
            Result := FIntfMock;
        end)
        .AsDefault;

    FTestContainer.Build;

    FSut := FTestContainer.Resolve<TMyClass>;
end;

procedure TMyTest.TearDown;
begin
    FreeAndNil(FSut);
    FIntfMock.Free;
    FreeAndNil(FTestContainer);
end;

procedure TMyTest.Test1;
var
    aSomething1: TSomething1;
begin
    // Arrange

    aSomething1 := TSomething1.Create;
    try
        aIntfMock.Setup.WillReturn(TValue.From<TSomething1>(aSomething1)).When.GetSomething1;

        // Act
        FSut.DoSomething;

        // Assert
        // ...

    finally
        FreeAndNil(aSomething1);
    end;
end;

procedure TMyTest.Test2;
begin
    // ...
end;

Thank you for your fast answer...


Solution

  • Short answer: No

    Long answer: The container has no supported possibility to unregister any registered types. This is because unregistering anything could cause cascading effects on other types and also on created instances (especially singletons).

    I always give the advice not to use GlobalContainer but create their own instance of TContainer. Then you can just throw the instance away and use a fresh one for your next test.

    You can look into our unit tests on how that can be done (there is TContainerTestCase class in Spring.Tests.Container.pas)

    Apart from the container you should avoid using singletons in tests anyway. Each unit test should be isolated. As soon as a global state is involved you might get side effects.