Как обмануть объект, созданный внутри тестируемого метода

man_luck спросил: 28 апреля 2018 в 08:45 в: c#

У меня есть код устаревшего кода и написание тестов для улучшений, которые я сделал для этого кода. У меня есть класс SiteSession и выведен интерфейс ISiteSession, поэтому зависимость может быть введена в вызывающий класс.

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

В вызывающем классе есть конструктор, где зависимость вводится в контроллер CustomerDetails, который находится под тестом

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);    //.....
    //...
    //...
}

Теперь мой тестовый метод издевался над зависимостями, и у меня нет проблем с любыми тестами, созданными для этого контроллера или для любой другой части кода. Но когда вызывается тестирование этого контроллера CustomerDetails, фактический вызов конструктора выполняется в классе SiteSession, и я не могу ввести макет и нарушить реальный вызов. Мой тестовый код выглядит следующим образом:

private Mock<ISiteSession> _siteSession;

В методе тестовой установки: _siteSession = new Mock<ISiteSession>();

И в методе тестирования: _siteSession.Setup(x => x.Token).Returns("TestToken");

Я пробовал что-то вроде:

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

Что явно не правильно из-за разных типов преобразования, и я не могу думать о том, как я может разбить класс SiteSession, чтобы фактический конструктор не вызывался. Я использую NInject, NUnit и Moq

1 ответ

Есть решение
Nkosi ответил: 28 апреля 2018 в 10:11

Это проблема дизайна. Путем ручного создания экземпляра в контроллере контроллер тесно связан с реализацией, что затрудняет издевательство над ним. Просмотрите свои варианты дизайна, поскольку текущий пример не очень ясен с точки зрения конечной цели.

Трудность в обеспечении надлежащего модульного теста должна быть прямым индикатором проблем дизайна.

Как говорится, на основе текущего дизайна, и если у вас есть контроль над контроллером, вам может понадобиться поставщик сеанса.

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

. Контроллер будет явно зависеть от

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);        //...
    }
}

Единичный тест теперь потребует от вас макета желаемого поведения.

//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
//...

Реализация провайдера может затем работать с реализацией проблемы

public ISiteSession CreateSiteSession(CustomerDetails customerDetails) {
    //...    return new SiteSession(customer.Username, customer.CustomerRef, ePermitService);
}