jmo
jmo

Reputation: 357

Mocking StandardKernal Interface with Ninject

I'm working on adding unit tests to some legacy ASP code with Moq and the Ninject.MockingKernal.

    public class BaseController : Controller
    {
        private IAuthenticationManager authenticationManager;
        public ILog log;

        public BaseController()
        {
            var kernel = new StandardKernel();
            kernel.Load(Assembly.GetExecutingAssembly());
            log = kernel.Get<ILog>();
        }
    }

The Log Interface:

public interface ILog
    {
        AuditTrail Create(AuditAction action, string description = null);
        AuditTrail Create(AuditAction action, long reservationId, string description = null);
        AuditTrail Create(IUser user, AuditAction action);
        AuditTrail Create(IUser user, AuditAction action, string description = null);
        AuditTrail Create(IUser user, AuditAction action, long reservationId, string description = null);
    }

I'm trying to mock the log instance that is set up from the kernel. This log is inherited by other controllers and is not injected. I want to be able to return a mock object when it's requested, much like I would do in other cases (such as returning a mock DatabaseContext from a factory).

I've looked at this How to do Setup of mocks with Ninject's MockingKernel (moq) and the GitHub example: https://github.com/ninject/Ninject.MockingKernel/wiki/Examples, as well as many others.

From what I've gathered, I need to do something along these lines:

mockingKernal = new MoqMockingKernel();
mockingKernal.Bind<ILog>().To<Logging.Log>();
var foo = mockingKernal.GetMock<ILog>();
foo.Setup(x => x.Create(It.IsAny<AuditAction>(), It.IsAny<long>(), It.IsAny<string>()));

However, if I run this, I get an error System.ArgumentException: Object instance was not created by Moq. From what I can find online, this is caused by the class having a parameter in the constructor, but in this case, the Log class does not.

Am I approaching this in the correct way? And if I am, what am I doing wrong? Any help would be greatly appreciated

Upvotes: 0

Views: 426

Answers (1)

Nkosi
Nkosi

Reputation: 247631

The above approach/design is going to cause all manner of head aches to maintain/test as the controller is tightly coupled to the kernel (IoC container) which basically does not allow one to be able to easily mock/replace it for testing.

Also note that the examples linked in question all have in common the ability to explicitly inject the dependencies into their subjects under test.

The above is basically using the kernel as a service locator.

Trying to put lipstick on that code may change its appearance but does nothing about the smell.

Ideally the design should be following the explicit dependency principle.

Methods and classes should explicitly require (typically through method parameters or constructor parameters) any collaborating objects they need in order to function correctly.

public class BaseController : Controller {
    private IAuthenticationManager authenticationManager;
    public ILog log;

    public BaseController(ILog log, IAuthenticationManager authenticationManager) {
        this.log = log;
        this.authenticationManager = authenticationManager;
    }
}

which would allow the dependencies to be mocked/faked/stubbed and injected into their dependents.

//Arrange
var logger = new Mock<ILog>();
logger
    .Setup(_ => _.Create(It.IsAny<AuditAction>(), It.IsAny<long>(), It.IsAny<string>()))
    .Return(new AuditTrail);

var controller = new BaseController(logger.Object, null);

//Act

//...

One should not be calling the container directly within classes but rather configure it at the composition root.

I suggest reviewing the current design and refactoring accordingly.

Upvotes: 1

Related Questions