I want to mock a Logger using NSubstitute. But instead of using Substitute.For, I want to use Substitute.ForPartsOf to:

  • both call the real implementation (to continue logging to the console)
  • and check the result using .Received, i.e. check whether something was logged.

The issue is, my Logger does have a factory method (Create), which returns a new instance of the logger.

Now, can I use this with NSubstitute to substitute it?

Here a full MWE.

I tried:

  • For and ForPartsOf, which always fail due to the private constructor
  • I switched to CallBase, but this also does not work, because I need to have a concrete instance
  • in the last try, ForCasting_UsingCallBase, I even tried casting the successfully created For substitute to the concrete type, to have a concrete type for the base method, which it complained, was missing; but in this case the casting fails
public interface ILogSettings { }

public interface ISomeLogger
{
    public void LogSomething(string anything);
}

public class SomeLogger : ISomeLogger
{
    private SomeLogger() =>
        throw new NotSupportedException("let's assume this constructor is not good to call");

    private SomeLogger(ILogSettings logSettings) =>
        CreateLogger(logSettings);

    private static void CreateLogger(ILogSettings logSettings)
    {
        Console.WriteLine();
        Console.WriteLine("Logger created.");

        // does something with logSettings, irrelevant here
    }

    /// <summary>
    /// Factory method here for creating logger!
    /// </summary>
    [PublicAPI]
    public static SomeLogger Create(ILogSettings? logSettings) =>
        logSettings != null
            ? new SomeLogger(logSettings)
            : throw new ArgumentNullException(nameof(logSettings));

    public void LogSomething(string anything) =>
        Console.WriteLine(anything);
}

[TestFixture]
public class SubstituteReproducerTest
{
    [Test]
    public void For_WorksButShowsNoLogging()
    {
        var logger = Substitute.For<ISomeLogger>();

        // This here is mocked, so it cannot/does not actually call the "real" method.
        // Thus, no logging is shown in the test.
        logger.LogSomething("sth");

        // I can, however, check the received call, of course.
        logger.ReceivedWithAnyArgs(1).LogSomething(default!);
    }

    [Test]
    public void ForPartsOf_OnInterface()
    {
        var logger = Substitute.ForPartsOf<ISomeLogger>();

        // This DOES NOT work!
        // Can only substitute for parts of classes, not interfaces or delegates. Try `Substitute.For<ISomeLogger> instead.
    }

    [Test]
    public void ForPartsOf_WithPrivateConstructor()
    {
        var logger = Substitute.ForPartsOf<SomeLogger>();

        // This DOES NOT work!
        // Could not find a parameterless constructor. (Parameter 'constructorArguments')
    }

    [Test]
    public void For_WithPrivateConstructor()
    {
        var logger = Substitute.For<SomeLogger>();

        // This DOES NOT work!
        // Could not find a parameterless constructor. (Parameter 'constructorArguments')
    }

    [Test]
    public void For_WithFactoryMethod()
    {
        var settings = Substitute.For<ILogSettings>();

        // This DOES NOT work!
        // The constructor args are supposed to be there.
        var logger = Substitute.For<SomeLogger>(() => SomeLogger.Create(settings));

        // Could not find a parameterless constructor. (Parameter 'constructorArguments')
    }

    [Test]
    public void ForPartsOf_WithFactoryMethod()
    {
        var settings = Substitute.For<ILogSettings>();

        // This DOES NOT work!
        // The constructor args are supposed to be there.
        var logger = Substitute.ForPartsOf<SomeLogger>(() => SomeLogger.Create(settings));

        // Could not find a parameterless constructor. (Parameter 'constructorArguments')
    }

    [Test]
    public void For_UsingCallBase()
    {
        var logger = Substitute.For<ISomeLogger>();
        logger.WhenForAnyArgs(x => x.LogSomething(default!)).CallBase();

        // This DOES NOT work!
        // NSubstitute.Exceptions.CouldNotConfigureCallBaseException : Cannot configure the base method call as base method implementation is missing. You can call base method only if you create a class substitute and the method is not abstract.
    }

    [Test]
    public void For_UsingCallBaseWithConcreteClass()
    {
        var logger = Substitute.For<SomeLogger>();

        // This DOES NOT work!
        // Could not find a parameterless constructor. (Parameter 'constructorArguments')

        // And ForPartsOf would fail to, as given and tried in ForPartsOf_WithPrivateConstructor.
    }

    [Test]
    public void ForCasting_UsingCallBase()
    {
        var logger = Substitute.For<ISomeLogger>();
        var loggerConcrete = logger as SomeLogger; // --> results in 'null'
        loggerConcrete.WhenForAnyArgs(x => x.LogSomething(default!)).CallBase();

        // This DOES NOT work!
        // NSubstitute.Exceptions.CouldNotConfigureCallBaseException : Cannot configure the base method call as base method implementation is missing. You can call base method only if you create a class substitute and the method is not abstract.
    }
}

I know I can make methods virtual to be substitut'able, but this does not work here, as the Logger/class is from a library I use.

1

There are 1 best solutions below

0
rklec On

So being at a dead-end, you apparently just cannot substitute factory method like this.

Instead I went with Substitute.For, which works. However, then I just re-implement the logging to overwrite it. E.g. you may just call Console.WriteLine:

    [Test]
    public void For_WithWhenForAnyArgs()
    {
        var logger = Substitute.For<ISomeLogger>();

        logger.WhenForAnyArgs(x => x.LogSomething(Arg.Any<string>()))
            .Do(arg => { Console.WriteLine(arg.Arg<string>()); });

        logger.LogSomething("sth");

        // I can check the received call, of course.
        logger.ReceivedWithAnyArgs(1).LogSomething(default!);

        // This WORKS!
        // and this would throw:
        // logger.Received(1).LogSomething("bad");
    } 

Thus, e.g. you may implement it like this in a bigger example:

    /// <summary>
    /// Modifies an existing <see cref="Substitute"/> from <see cref="NSubstitute"/> to only log to the console.
    /// This allows you to still assert things on it like checking for <c>.Received</c> calls etc.
    /// </summary>
    /// <remarks><b>Important:</b> You need to have already created a working substitute.
    /// </remarks>
    /// <param name="logger">the substitute to modify</param>
    /// <exception cref="NotSupportedException">when it is not substitute</exception>
    [PublicAPI]
    public static void LogToConsole(this ILogger logger)
    {
        if (!logger.IsSubstitute())
            throw new NotSupportedException($"{nameof(ILogger)} must be a {nameof(Substitute)}.");

        logger.WhenForAnyArgs(x => x.Debug(Arg.Any<string>()))
            .Do(arg => { Log.Logger.Debug(arg.Arg<string>()); });

        logger.WhenForAnyArgs(x => x.Debug(Arg.Any<string>(), Arg.Any<Exception>()))
            .Do(arg => { Log.Logger.Debug(arg.Arg<string>(), arg.Arg<Exception>()); });

        logger.WhenForAnyArgs(x => x.Info(Arg.Any<string>()))
            .Do(arg => { Log.Logger.Information(arg.Arg<string>()); });

        logger.WhenForAnyArgs(x => x.Info(Arg.Any<string>(), Arg.Any<Exception>()))
            .Do(arg => { Log.Logger.Information(arg.Arg<string>(), arg.Arg<Exception>()); });

        logger.WhenForAnyArgs(x => x.Warn(Arg.Any<string>()))
            .Do(arg => { Log.Logger.Warning(arg.Arg<string>()); });

        logger.WhenForAnyArgs(x => x.Warn(Arg.Any<string>(), Arg.Any<Exception>()))
            .Do(arg => { Log.Logger.Warning(arg.Arg<string>(), arg.Arg<Exception>()); });

        logger.WhenForAnyArgs(x => x.Error(Arg.Any<string>()))
            .Do(arg => { Log.Logger.Error(arg.Arg<string>()); });

        logger.WhenForAnyArgs(x => x.Error(Arg.Any<string>(), Arg.Any<Exception>()))
            .Do(arg => { Log.Logger.Error(arg.Arg<string>(), arg.Arg<Exception>()); });

        logger.WhenForAnyArgs(x => x.Fatal(Arg.Any<string>()))
            .Do(arg => { Log.Logger.Fatal(arg.Arg<string>()); });

        logger.WhenForAnyArgs(x => x.Fatal(Arg.Any<string>(), Arg.Any<Exception>()))
            .Do(arg => { Log.Logger.Fatal(arg.Arg<string>(), arg.Arg<Exception>()); });
    }

This uses this helper method as an extension to check whether it is a valid Substitute. And in this case I use Log.Logger from SeriLog.