【问题标题】:Mock nested dependencies with MOQ使用 MOQ 模拟嵌套依赖项
【发布时间】:2016-01-28 02:34:16
【问题描述】:

我假设 MOQ 会自动为任何嵌套的依赖项创建 Mocks。

我正在对 ASP.Net MVC 控制器进行单元测试:

public class TransactionController : Controller
{
    private readonly ITransactionService _transactionService;
    private readonly SearchPanelVmBuilder _searchPanelVmBuilder;
    private readonly TransactionVmsBuilder _transactionVmsBuilder;

    public TransactionController(TransactionVmsBuilder transactionVmsBuilder, ITransactionService transactionService, SearchPanelVmBuilder searchPanelVmBuilder)
    {
        _transactionVmsBuilder = transactionVmsBuilder;
        _transactionService = transactionService;
        _searchPanelVmBuilder = searchPanelVmBuilder;
    }

    // other methods omitted for brevity

    public PartialViewResult SearchPanel()
    {
        var vm = _searchPanelVmBuilder.BuildVm();

        return PartialView("_SearchPanel", vm);
    }
}

单元测试代码:

[Fact]
public void SeachPanel_Calls_BuildSearchPanelVm()
{
    // Arrange
    var mockTransService = new Mock<ITransactionService>();
    var mockTransVmsBuilder = new Mock<TransactionVmsBuilder>();
    var mockSearchPanelVmBuilder = new Mock<SearchPanelVmBuilder>();
    var controller = new TransactionController(mockTransVmsBuilder.Object, mockTransService.Object, mockSearchPanelVmBuilder.Object);

    // Act
    controller.SearchPanel();

    // Assert
    mockSearchPanelVmBuilder.Verify(x => x.BuildVm());
}

起订量抱怨:

无法实例化类的代理:MCIP.Web.UI.ViewModelBuilders.Singular.SearchPanelVmBuilder。 找不到无参数构造函数。

无法为其实例化代理的类:

public class SearchPanelVmBuilder
{
    private readonly ITransactionTypeService _transactionTypeService;
    private readonly TransactionTypeVmBuilder _transactionTypeVmBuilder;
    private readonly UserProvider _userProvider;

    public SearchPanelVmBuilder(
        UserProvider userProvider,
        ITransactionTypeService transactionTypeService,
        TransactionTypeVmBuilder transactionTypeVmBuilder
        )
    {
        _userProvider = userProvider;
        _transactionTypeService = transactionTypeService;
        _transactionTypeVmBuilder = transactionTypeVmBuilder;
    }

    public virtual SearchPanelVm BuildVm()
    {
        return new SearchPanelVm
        {
            Userlist = _userProvider.GetOperators(),
            TransactionTypes =
                _transactionTypeService.GetAll().Select(x => _transactionTypeVmBuilder.BuildVmFromModel(x)).ToList()
        };
    }
}

其对应的依赖:

public class UserProvider
{
    private static int retryCount;

    public virtual List<string> GetOperators()...

    public virtual List<string> GetGroupsForUser(WindowsIdentity identity)...
}

public interface ITransactionTypeService
{
    List<TransactionType> GetAll();
}

public class TransactionTypeVmBuilder
{
    public virtual TransactionTypeVm BuildVmFromModel(TransactionType transactionType)...
}

我做错了吗?

我是否必须明确告诉 MOQ 尝试自动模拟嵌套依赖项?

或者我是否必须明确设置嵌套的 Mocks - 像这样:

var mockUserProvider = new Mock<UserProvider>();
var mockTransTypeService = new Mock<ITransactionTypeService>();
var mockTransactionTypeVmBuilder = new Mock<TransactionTypeVmBuilder>();
var mockSearchPanelVmBuilder = new Mock<SearchPanelVmBuilder>(mockUserProvider.Object, mockTransTypeService.Object, mockTransactionTypeVmBuilder.Object);

【问题讨论】:

  • 就我个人而言,我尝试让我的类依赖于接口而不是具体的类,这样我就可以在单元测试期间嘲笑我的心,并真正限制每个测试的范围。
  • 简而言之,拥有接口并不能解决问题。没有接口的类有虚方法——它们仍然可以像接口一样被模拟出来。由于它们永远不会有替代实现,因此纯粹用于模拟的接口是不必要的。

标签: c# unit-testing moq


【解决方案1】:

是的,您的假设是正确的。因为SearchPanelVmBuilder这个类没有提供无参构造函数,所以这个类的Mock不能这样创建:

var mockSearchPanelVmBuilder = new Mock&lt;SearchPanelVmBuilder&gt;()

导致异常:Could not find a parameterless constructor


而是通过提供所有参数来创建 Mock,如下所示:

UserProvider userProvider = new UserProvider();
Mock<ITransactionTypeService> transactionTypeService = new Mock<ITransactionTypeService>();
TransactionTypeVmBuilder transactionTypeVmBuilder = new TransactionTypeVmBuilder();

// Use constructor with parameters here because SearchPanelVmBuilder 
// doesn't have parameterless constructor
var mockSearchPanelVmBuilder = new Mock<SearchPanelVmBuilder>(
    userProvider, transactionTypeService.Object, transactionTypeVmBuilder);

var mockTransService = new Mock<ITransactionService>();
var mockTransVmsBuilder = new Mock<TransactionVmsBuilder>();

var controller = new TransactionController(
    mockTransVmsBuilder.Object, 
    mockTransService.Object, 
    mockSearchPanelVmBuilder.Object);

然后可以创建控制器TransactionController的实例,并可以在其上调用方法SearchPanel

【讨论】:

  • 感谢您的回答!所以 Moq 真的没有办法像 StructureMap 那样自动解决嵌套依赖关系吗?
  • 不客气!据我了解构造函数在 c# 中是如何工作的,没有。但是检查例如this answer。或this.
【解决方案2】:

在这种情况下,您要测试的只是调用 BuildVM 并返回结果,因此您无需模拟内部依赖项。在调用 SearchPanel 方法之前,您确实需要在 Arrange 部分中为 BuildVM 方法设置一个返回值。

mockSearchPanelVmBuilder.Setup(x => x.BuildVM()).Returns(mockSearchPanelVM);

因为如果你不设置返回值,你会模拟一个类和虚拟方法,实际的实现将被运行。使用接口会抛出错误,指示您应该设置返回值。

【讨论】:

    猜你喜欢
    • 2011-06-15
    • 2017-07-15
    • 2020-11-26
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-23
    • 1970-01-01
    相关资源
    最近更新 更多