Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock an object instantiated inside a method under test

I have some legacy code and writing tests on the enhancements I have made on that code. I have a class SiteSession and have extracted an interface ISiteSession so the dependency could be injected in the calling class.

public class SiteSession : ISiteSession
{
    public SiteSession(string userName, string externalRef, IePermitProAdapterClient ePermitService)
    {
        //.......
    }

    //...
}

The calling class has a constructor where the dependency is injected in a controller CustomerDetails which is under test

private readonly ICustomerDetails _customerDetails;
private ISiteSession _siteSession;

public SsoController(ICustomerDetails customerDetails, ISiteSession siteSession)
{
    _customerDetails = customerDetails;

    _siteSession = siteSession;
}

public ActionResult CustomerDetails(CustomerDetails customerDetails)
{
    //.....  
    //...
    //...    

    _siteSession = new SiteSession(customer.Username, customer.CustomerRef, ePermitService);

    //.....
    //...
    //...
}

Now my test method has mocked the dependencies and I have no problem with any of the tests created for this controller or for any other part of the code. But when the tests on this controller CustomerDetails is called, the actual constructor call is made to SiteSession class and I am unable to inject the mock and break the real call. My test code is as follows:

private Mock<ISiteSession> _siteSession;

In the test setup method: _siteSession = new Mock<ISiteSession>();

And in the test method: _siteSession.Setup(x => x.Token).Returns("TestToken");

I tried something like:

 _siteSession = new Mock<SiteSession>(_customer.Object.Username, _customer.Object.CustomerRef, null);

Which is obviously not correct due to different types in conversion and I am unable to think of how I can Mock the SiteSession class so the actual constructor is not called. I am using NInject, NUnit, and Moq

like image 547
man_luck Avatar asked Oct 17 '25 15:10

man_luck


1 Answers

This is a design issue. By manually creating the instance in the controller the controller is tightly coupled to the implementation which makes mocking it very difficult. Review your design choices as the current example is not very clear in terms of what is the end goal.

The difficulty in providing a proper unit test should be a direct indicator of the design issues.

That being said, based on the current design, and If you have control of the controller you may have need for a session provider

public interface ISiteSessionProvider {
    ISiteSession CreateSiteSession(CustomerDetails customerDetails);
}

Which the controller would explicitly depend on

public class SsoController: Controller {
    private readonly ICustomerDetails _customerDetails;
    private readonly ISiteSessionProvider siteSessionProvider;

    public SsoController(ICustomerDetails customerDetails, ISiteSessionProvider siteSessionProvider) {
        _customerDetails = customerDetails;
        this.siteSessionProvider = siteSessionProvider;
    }

    public ActionResult CustomerDetails(CustomerDetails customerDetails) {
        //...

        ISiteSession siteSession = siteSessionProvider.CreateSiteSession(customerDetails);

        //...
    }
}

A unit test would now require you mock up the desired behavior

//Arrange
var sessionMock = new Mock<ISiteSession>();
sessionMock.Setup(_ => _.Token).Returns("TestToken");

var providerMock = new Mock<ISiteSessionProvider>();
providerMock
    .Setup(_ => _.CreateSiteSession(It.IsAny<CustomerDetails>()))
    .Returns(sessionMock.Object);

var controller = new SsoController(Mock.Of<ICustomerDetails>(), providerMock.Object);

//Act
var result = controller.CustomerDetails(...);

//Assert
//...

The implementation of the provider can then deal with implementation concerns

public ISiteSession CreateSiteSession(CustomerDetails customerDetails) {
    //...

    return new SiteSession(customer.Username, customer.CustomerRef, ePermitService);
}
like image 87
Nkosi Avatar answered Oct 20 '25 05:10

Nkosi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!