如何模拟在测试方法中实例化的对象

4

我有一些旧代码,并且正在对我所做的增强进行测试。我有一个名为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
提供适当的单元测试的难度应该是设计问题的直接指标。 - Nkosi
1
这正是我所想的,但我仍然不确定。我认为这就是为遗留代码编写单元测试的问题所在,我怀疑可能有一种既定的方法来解决这个问题。在这种情况下,我可以更改设计,但是对于我们拥有大量遗留代码的情况可能是不可能的。难道真的没有办法使用参数化构造函数模拟在方法内实例化的对象吗? - man_luck
2
依赖具体类通常是为遗留代码创建测试的最大问题。幸运的是,通过注入工厂/提供者模式,这相对容易解决。 - Lasse V. Karlsen
1个回答

4

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

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

话虽如此,基于当前的设计,如果您控制控制器,则可能需要会话提供程序。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接