代码之家  ›  专栏  ›  技术社区  ›  JTech

用MOQ模拟嵌套依赖项

  •  9
  • JTech  · 技术社区  · 9 年前

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

    我正在对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());
    }
    

    MOQ抱怨:

    无法实例化类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);
    
    2 回复  |  直到 9 年前
        1
  •  3
  •   gembird    9 年前

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

    var mockSearchPanelVmBuilder = new Mock<SearchPanelVmBuilder>()

    原因异常: 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 可以调用它。

        2
  •  1
  •   chief7    9 年前

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

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

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