crazyTech
crazyTech

Reputation: 1477

How to write Mock Unit Test Code that will Test a class method that downcasts an input argument interface as a concrete class?

I'm writing mock unit test code that will Test a class method that downcasts an input argument interface as a concrete class.

Here is the “System Under Test(SUT)” class called CarWrapperFactory:

public class CarWrapperFactory : ICarWrapperFactory
{
    public ICarWrapper CreateCarWrapper(ICarConfigurationWrapper carConfigurationWrapper)
    {

        // downcasting occurring on next line, and also subsequently invocation of "protected internal" method
        CarConfiguration loggerConfiguration = ((CarConfigurationWrapper)carConfigurationWrapper).GetCarConfigurationAdaptee();

        return new CarWrapper(carConfiguration.CreateCar());
    }
}

In the aforementioned CarWrapperFactory SUT class, there is downcasting code because it invokes a “protected internal” method called GetCarConfigurationAdaptee.

In my NSubstitute-based Mock Unit Test code, I start off with the following:

ICarConfigurationWrapper carConfigurationWrapperMock = Substitute.For<ICarConfigurationWrapper>();

 // Fill-in-blank code….how to mock the “protected internal” method called GetCarConfigurationAdaptee

ICarWrapperFactory carWrapperFactorySUT = new CarWrapperFactory();

  ICarWrapper carWrapper = carWrapperFactorySUT. CreateCarWrapper(carConfigurationWrapperMock);

How can I write the code for the "Fill-in-blank" code above?

PS: Essentially, I'm trying to adapt/wrap 3rd-party open source Serilog's modules. It's Serilog's bad design. Inside Serilog's LoggerConfiguration , there is a method called "public Logger CreateLogger()" Serilog does Not have a factory for Logger, it instantiates a Logger based using methods in the LoggerConfiguration. It does Not make sense. Here is their code:

https://github.com/serilog/serilog/blob/dev/src/Serilog/LoggerConfiguration.cs

Upvotes: -1

Views: 54

Answers (1)

Sergey K
Sergey K

Reputation: 4114

First of all ICarConfigurationWrapper should contain all methods as a contract of the class

it is bad code design to call protected methods, consider them as private, so you need pull this protected method up in the interface. and you will not need to cast nothing and in your Unit tests you can easy substitute with mock implementation

Protected methods only visible in derived classes

PS carConfiguration.CreateCar() it is not rensposiblity of configuration to create the cars it should be CarFactory class

I would refactor the code in the following way

public class CarWrapperFactory : ICarFactory
{
    private readonly ICarFactory _factory;

    public CarWrapperFactory(ICarFactory factory)
    {
        _factory = factory;
    }
    public ICar CreateCar(ICarConfiguration configuration)
    {
       //additional logic here
        var car = _factory.CreateCar(configuration);

        return car;
    }
}

the Idea or Wrapper Design pattern to have the same interface but add additional functionality so your Wrapper should implement the same interface as the class which it suppose to wrap

UPDATE: as per discussion in one of my project I've used wrapper around Serilog

using Serilog;
using Serilog.Formatting.Compact;

namespace Framework.Infrastructure.Logging;

class Logger : ILogger
{
    private const string Context = "TraceId";
    private readonly Serilog.Core.Logger _logger;
    private string _traceId;

    public Logger()
    {
        _logger = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .WriteTo.Console( new RenderedCompactJsonFormatter())
            .CreateLogger();
    }

    public void AddTraceId(string traceId)
    {
        _traceId = traceId;
    }

    public void LogInfo(string message)
    {
        _logger.ForContext(Context, _traceId).Information(message);
    }
    public void LogInfo(string messageTemplate, params object[] propertyValues)
    {
        _logger.ForContext(Context, _traceId).Information(messageTemplate, propertyValues);
    }

    public void LogTrace(string message)
    {
        _logger.ForContext(Context, _traceId).Debug(message);
    }

    public ITimeLogger LogPerformance(string message)
    {
        return new TimeLogger(this, message);
    }

    public void LogError(Exception exception, string messageTemplate)
    {
        _logger.ForContext(Context, _traceId).Error(exception, messageTemplate);
    }
}

here is interface

public interface ILogger
{
    void AddTraceId(string traceId);
    void LogInfo(string message);
    void LogTrace(string message);
    ITimeLogger LogPerformance(string message);
    void LogError(Exception exception, string messageTemplate);
    void LogInfo(string messageTemplate, params object[] propertyValues);
}

hope this works for you

Upvotes: 1

Related Questions