• NSubstitute完全手册(十七)参数匹配器上的操作


    除了指定调用,当替代实例接收到了匹配的调用时,参数匹配器还可以用于执行特定的操作,并指定操作参数。这是一个相当罕见的需求,但在某些情况下可以使测试程序变得简单一些。

    警告:一旦我们为替代实例添加有意义的行为,我们的测试和代码将面临过度指定和紧耦合的风险。对于类似的测试,通过抽象来更好地封装行为可能是更好的选择,甚至可以使用真实的协作对象并切换至 coarser-grained 测试。

    调用回调函数

    假设被测试类需要调用一个依赖对象的方法,并为其提供了一个回调函数,当依赖对象调用结束时通过回调来通知该类。当替代实例被调用时,我们可以使用 Arg.Invoke() 来立即调用这个回调。

    让我们来看一个例子。比如说我们要测试 OrderPlacedCommand,其需要使用 IOrderProcessor 来处理订单,当处理成功地完成时使用 IEvents 来引发事件通知。

     1     public interface IEvents
     2     {
     3       void RaiseOrderProcessed(int orderId);
     4     }
     5 
     6     public interface ICart
     7     {
     8       int OrderId { get; set; }
     9     }
    10 
    11     public interface IOrderProcessor
    12     {
    13       void ProcessOrder(int orderId, Action<bool> orderProcessed);
    14     }
    15 
    16     public class OrderPlacedCommand
    17     {
    18       IOrderProcessor orderProcessor;
    19       IEvents events;
    20       public OrderPlacedCommand(IOrderProcessor orderProcessor, IEvents events)
    21       {
    22         this.orderProcessor = orderProcessor;
    23         this.events = events;
    24       }
    25       public void Execute(ICart cart)
    26       {
    27         orderProcessor.ProcessOrder(
    28             cart.OrderId,
    29             wasOk => { if (wasOk) events.RaiseOrderProcessed(cart.OrderId); }
    30         );
    31       }
    32     }

    在测试中,可以使用 Arg.Invoke() 来模拟 IOrderProcessor 处理订单结束的情况,并调用回调函数来通知调用方已经处理结束。

     1     [TestMethod]
     2     public void Test_ActionsWithArgumentMatchers_InvokingCallbacks()
     3     {
     4       // Arrange
     5       var cart = Substitute.For<ICart>();
     6       var events = Substitute.For<IEvents>();
     7       var processor = Substitute.For<IOrderProcessor>();
     8       cart.OrderId = 3;
     9       // 设置 processor 当处理订单ID为3时,调用回调函数,参数为true
    10       processor.ProcessOrder(3, Arg.Invoke(true));
    11 
    12       // Act
    13       var command = new OrderPlacedCommand(processor, events);
    14       command.Execute(cart);
    15 
    16       // Assert
    17       events.Received().RaiseOrderProcessed(3);
    18     }

    这里我们构造了 processor,用于处理 ID 为 3 的订单,并调用回调函数。我们使用 Arg.Invoke(true) 来传递 true 给回调函数。

    Arg.Invoke 有几个重载方法,可以用于调用参数数量和类型不同的回调函数。我们也可以使用 Arg.InvokeDelegate 来调用定制的委托类型(那些不只是简单的 Action 类型的委托)。

    执行带参数的操作

    有时我们可能不想立即就调用回调函数。或者可能我们想存储所有的实例到一个特殊的参数,并将该参数传递给一个方法。甚至我们只是想捕获某个参数,以便后期查看。我们可以使用 Arg.Do 来完成这些目的。Arg.Do 会为每个匹配的调用来执行给定参数的操作。

     1     public interface ICalculator
     2     {
     3       int Multiply(int a, int b);
     4     }
     5 
     6     [TestMethod]
     7     public void Test_ActionsWithArgumentMatchers_PerformingActionsWithArgs()
     8     {
     9       var calculator = Substitute.For<ICalculator>();
    10 
    11       var argumentUsed = 0;
    12       calculator.Multiply(Arg.Any<int>(), Arg.Do<int>(x => argumentUsed = x));
    13 
    14       calculator.Multiply(123, 42);
    15 
    16       Assert.AreEqual(42, argumentUsed);
    17     }

    这里,Multiply 方法的第一个参数可为任意值,第二个参数将被传递值一个 argumentUsed 变量。当我们想断言某参数的多个属性时,这个功能是非常有用的。

     1     [TestMethod]
     2     public void Test_ActionsWithArgumentMatchers_PerformingActionsWithAnyArgs()
     3     {
     4       var calculator = Substitute.For<ICalculator>();
     5 
     6       var firstArgsBeingMultiplied = new List<int>();
     7       calculator.Multiply(Arg.Do<int>(x => firstArgsBeingMultiplied.Add(x)), 10);
     8 
     9       calculator.Multiply(2, 10);
    10       calculator.Multiply(5, 10);
    11 
    12       // 由于第二个参数不为10,所以不会被 Arg.Do 匹配
    13       calculator.Multiply(7, 4567); 
    14 
    15       CollectionAssert.AreEqual(firstArgsBeingMultiplied, new[] { 2, 5 });
    16     }

    在此例中,当调用 Multiply 方法第二个参数是 10 时,不管第一个 int 类型的参数的值是多少,Arg.Do 都会将其添加到一个列表当中。

    参数操作和调用指定参数

    就像 Arg.Any<T>() 参数匹配器一样,当参数的类型为 T 时,参数操作会调用一个指定操作(所以也可以用于设置返回值检查接收到的调用)。它只是多了个能与匹配指定规格的调用的参数交互的功能。

     1     [TestMethod]
     2     public void Test_ActionsWithArgumentMatchers_ArgActionsCallSpec()
     3     {
     4       var calculator = Substitute.For<ICalculator>();
     5 
     6       var numberOfCallsWhereFirstArgIsLessThan0 = 0;
     7 
     8       // 指定调用参数:
     9       // 第一个参数小于0
    10       // 第二个参数可以为任意的int类型值
    11       // 当此满足此规格时,为计数器加1。
    12       calculator
    13         .Multiply(
    14           Arg.Is<int>(x => x < 0),
    15           Arg.Do<int>(x => numberOfCallsWhereFirstArgIsLessThan0++)
    16         ).Returns(123);
    17 
    18       var results = new[] {
    19         calculator.Multiply(-4, 3),
    20         calculator.Multiply(-27, 88),
    21         calculator.Multiply(-7, 8),
    22         calculator.Multiply(123, 2) // 第一个参数大于0,所以不会被匹配
    23       };
    24 
    25       Assert.AreEqual(3, numberOfCallsWhereFirstArgIsLessThan0); // 4个调用中有3个匹配上
    26       CollectionAssert.AreEqual(results, new[] { 123, 123, 123, 0 }); // 最后一个未匹配
    27     }

    NSubstitute 完全手册

  • 相关阅读:
    Qt使用第三方库3rdparty
    Qt5.5以来对Network的改进(包括对SSL的功能支持,HTTP的重定向等等)
    ddd
    C# ICSharpCode.SharpZipLib
    OWIN
    C#/.NET code
    ABP启动配置
    Oracle表空间及分区表
    Grunt和Gulp构建工具在Visual Studio 2015中的高效的应用
    WebAPI使用多个xml文件生成帮助文档
  • 原文地址:https://www.cnblogs.com/gaochundong/p/nsubstitute_actions_with_argument_matchers.html
Copyright © 2020-2023  润新知