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:
ForandForPartsOf, 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 createdForsubstitute 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.
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 callConsole.WriteLine:Thus, e.g. you may implement it like this in a bigger example:
This uses this helper method as an extension to check whether it is a valid Substitute. And in this case I use
Log.Loggerfrom SeriLog.