【问题标题】:How to mock an object instantiated inside a method under test如何模拟在被测方法中实例化的对象
【发布时间】:2018-05-22 10:25:50
【问题描述】:

我有一些遗留代码并针对我对该代码所做的增强编写测试。我有一个类 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&lt;ISiteSession&gt;();

并且在测试方法中:_siteSession.Setup(x =&gt; x.Token).Returns("TestToken");

我尝试了类似的方法:

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

由于转换中的类型不同,这显然是不正确的,我无法想到如何模拟 SiteSession 类,以便不调用实际的构造函数。我正在使用 NInject、NUnit 和 Moq

【问题讨论】:

  • 这是一个设计问题。通过在控制器中手动创建实例,控制器与实现紧密耦合,这使得模拟变得非常困难。查看您的设计选择,因为当前示例在最终目标方面不是很清楚。
  • 提供适当单元测试的难度应该是设计问题的直接指标。
  • 这正是我的想法,但仍然不确定。我认为这是为遗留代码编写单元测试的问题,我怀疑可能有一种既定的方法来解决这个问题。在这种情况下,我可以更改设计,但是我们有大量的遗留代码可能是不可能的。真的没有办法模拟使用参数化构造函数在方法内部实例化的对象吗?
  • 对具体类的依赖通常是为遗留代码创建测试的首要问题。值得庆幸的是,通过注入工厂/提供者模式相对容易对抗。

标签: c# unit-testing nunit moq


【解决方案1】:

这是一个设计问题。通过在控制器中手动创建实例,控制器与实现紧密耦合,这使得模拟变得非常困难。检查您的设计选择,因为当前示例在最终目标方面不是很清楚。

提供适当单元测试的难度应该是设计问题的直接指标。

话虽如此,根据当前的设计,如果您可以控制控制器,您可能需要会话提供程序

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    相关资源
    最近更新 更多