Unit test a void method with Mock?

3k views Asked by At

I want to test a void method with Mock.

 public class ConsoleTargetBuilder : ITargetBuilder
{
    private const string CONSOLE_WITH_STACK_TRACE = "consoleWithStackTrace";
    private const string CONSOLE_WITHOUT_STACK_TRACE = "consoleWithoutStackTrace";
    private LoggerModel _loggerModel;
    private LoggingConfiguration _nLogLoggingConfiguration;

    public ConsoleTargetBuilder(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration)
    {
        _loggerModel = loggerModel;
        _nLogLoggingConfiguration = nLogLoggingConfiguration;
    }

    public void AddNLogConfigurationTypeTagret()
    {
        var consoleTargetWithStackTrace = new ConsoleTarget();
        consoleTargetWithStackTrace.Name = CONSOLE_WITH_STACK_TRACE;
        consoleTargetWithStackTrace.Layout = _loggerModel.layout + "|${stacktrace}";
        _nLogLoggingConfiguration.AddTarget(CONSOLE_WITH_STACK_TRACE, consoleTargetWithStackTrace);

        var consoleTargetWithoutStackTrace = new ConsoleTarget();
        consoleTargetWithoutStackTrace.Name = CONSOLE_WITHOUT_STACK_TRACE;
        consoleTargetWithoutStackTrace.Layout = _loggerModel.layout;
        _nLogLoggingConfiguration.AddTarget(CONSOLE_WITHOUT_STACK_TRACE, consoleTargetWithoutStackTrace);
    }

The thing is I am not sure how to test it. I have my primary code.

public class ConsoleTargetBuilderUnitTests
{
    public static IEnumerable<object[]> ConsoleTargetBuilderTestData
    {
        get
        {
            return new[]
            {
                 new object[]
                {
                    new LoggerModel(),
                    new LoggingConfiguration(),
                    2
                }
            };
        }
    }
    [Theory]
    [MemberData("ConsoleTargetBuilderTestData")]
    public void ConsoleTargetBuilder_Should_Add_A_Console_Target(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration, int expectedConsoleTargetCount)
    {
        // ARRANGE
        var targetBuilderMock = new Mock<ITargetBuilder>();
        targetBuilderMock.Setup(x => x.AddNLogConfigurationTypeTagret()).Verifiable();
        // ACT
        var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);
        // ASSERT
        Assert.Equal(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
    }

Please guide me to the right direction.

4

There are 4 answers

3
Philip Stuyck On BEST ANSWER

It looks to me like you don't need a mock at all. You are testing AddNLogConfigurationTypeTagret. Not the constructor.

[Theory]
[MemberData("ConsoleTargetBuilderTestData")]
public void ConsoleTargetBuilder_Should_Add_A_Console_Target(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration, int expectedConsoleTargetCount)
{
    // ARRANGE
    var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);
    // ACT
    consoleTargetBuilder.AddNLogConfigurationTypeTagret();
    // ASSERT
    Assert.Equal(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
}

If you don't want to call AddNLogConfigurationTypeTagret yourself, it should be called in the constructor. This changes the test to this :

[Theory]
[MemberData("ConsoleTargetBuilderTestData")]
public void ConsoleTargetBuilder_Should_Add_A_Console_Target(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration, int expectedConsoleTargetCount)
{
    // ARRANGE
    // ACT
    var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);
    // ASSERT
    Assert.Equal(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
}

and the class constructor should then be :

public ConsoleTargetBuilder(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration)
{
    _loggerModel = loggerModel;
    _nLogLoggingConfiguration = nLogLoggingConfiguration;
    AddNLogConfigurationTypeTagret();
}
2
Stuart Grassie On
// Assert
targetBuilderMock.Verify(x => x.AddNLogConfigurationTypeTagret(), Times.Once());
0
forsvarir On

Generally speaking, you shouldn't be mocking the class you want to test, you should be mocking it's dependencies. When you start mocking the class you're trying to test things often get entwined leading to brittle tests and it's difficult to determine if you're testing your code, or you mocking setup, or both.

The method AddNLogConfigurationTypeTagret is a public method on the class you're testing, so as @Philip has said, you should probably just be calling it from your test, or it should be called from your constructor. If you goal is for it to be called from your constructor, then I'd suggest that it should probably be a private method unless there's a reason for it to be called from outside the class.

As far as testing the effects of a void method call, you're interested in the state change caused by the method. In this instance, whether or not the _nLoggingConfiguration has two targets added with the correct attributes. You don't show us the nLoggingConfiguration.AllTargets property, but I would assume from the fact that you're using the Count property in your test, that you could simply inspect the items in the list to confirm that the correct name and layout have been added to the correct target type.

4
gembird On

Please guide me to the right direction, I want two things. A) the method was called. B) two targets were added. So the expected count is 2.

A) If you wish to verify that method AddNLogConfigurationTypeTagret was called, then you need to test it on the code which is calling this method. E.g. something like the class ConsoleTargetBuilderClient which is an hypothetical example of a class which is using the ITargetBuilder (in your own code you should have some place where the class ConsoleTargetBuilder is used).

public class ConsoleTargetBuilderClient
{
    private ITargetBuilder _builder;

    public ConsoleTargetBuilderClient(ITargetBuilder builder)
    {
        _builder = builder;
    }

    public void DoSomething()
    {
        _builder.AddNLogConfigurationTypeTagret();
    }
}

Then you can have a test which verifies that the method DoSomething called the method AddNLogConfigurationTypeTagret. For that purpose you can use a mock of ConsoleTargetBuilder because you test interaction with it. Test might look like this:

    public void DoSomething_WhenCalled_AddNLogConfigurationTypeTagretGetsCalled()
    {
        // Arrange
        bool addNLogConfigurationTypeTagretWasCalled = false;
        Mock<ITargetBuilder> targetBuilderMock = new Mock<ITargetBuilder>();
        targetBuilderMock.Setup(b => b.AddNLogConfigurationTypeTagret())
            .Callback(() => addNLogConfigurationTypeTagretWasCalled = true);
        ConsoleTargetBuilderClient client = new ConsoleTargetBuilderClient(targetBuilderMock.Object);

        // Act
        client.DoSomething();

        // Assert
        Assert.IsTrue(addNLogConfigurationTypeTagretWasCalled);
    }

B) If you wish to verify that the targets were added you need to test the code itself (not mock of it). Test might look like this:

public void AddNLogConfigurationTypeTagret_WhenCalled_ConsoleTargetsAdded()
{
    // Arrange
    const int expectedConsoleTargetCount = 2;
    var loggerModel = new LoggerModel();
    var nLogLoggingConfiguration = new LoggingConfiguration();
    var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);

    // Act
    consoleTargetBuilder.AddNLogConfigurationTypeTagret();

    // Assert
    Assert.AreEqual<int>(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
}

Note: google value-based vs. state-based vs. interaction testing.