• .net core 单元测试之 JustMock第二篇


    JustMock标记方法

    上篇文章在举例子的时候使用了returns的标记方法,JustMock还有很多标记方法:

    • CallOriginal
      跟Behaviors里的CallOriginal差不多意思,被调用时执行原始的方法和属性的实现。
    • DoNothing
      忽略对方法或者属性的调用。
    • DoInstead
      替换原来方法的调用,或者属性的设置。
    • MustBeCalled
      被标记的方法必须调用,否则会抛出异常。
    • Raise
      当我们需要测试Event是否执行时,我们可以使用Raise来引发Events。
      例子:
        public delegate void CustomEvent(string value);
    
        public interface IFoo
        {
            event CustomEvent CustomEvent;
        }
    
            //测试方法:
            [Test]
            public void ShouldInvokeMethodForACustomEventWhenRaised()
            {
                string expected = "ping";
                string actual = string.Empty;
    
                var foo = Mock.Create<IFoo>();
    
                foo.CustomEvent += delegate (string s)
                {
                    actual = s;
                };
    
                //引发事件,并传递参数
                Mock.Raise(() => foo.CustomEvent += null, expected);
    
                Assert.AreEqual(expected, actual);
            }
    
    • Raises
      Raises用在调用方法后出发事件。
      例子:
        //使用的接口
        public delegate void CustomEvent(string value, bool called);
    
        public delegate void EchoEvent(bool echoed);
    
        public delegate void ExecuteEvent();
    
        public interface IFoo
        {
            event CustomEvent CustomEvent;
            event EchoEvent EchoEvent;
            event ExecuteEvent ExecuteEvent;
    
            void RaiseMethod();
            string Echo(string arg);
            void Execute();
            void Execute(string arg);
        }
    

    当调用方法时触发:

        //测试方法
            [Test]
            public void ShouldRaiseCustomEventOnMethodCall()
            {
                string actual = string.Empty;
                bool isCalled = false;
    
                var foo = Mock.Create<IFoo>();
    
                foo.CustomEvent += (s, c) => { actual = s; isCalled = c; };
                Mock.Arrange(() => foo.RaiseMethod()).Raises(() => foo.CustomEvent += null, "ping", true);
    
                foo.RaiseMethod();//调用方法,触发事件
    
                Assert.AreEqual("ping", actual);
                Assert.IsTrue(isCalled);
            }
    

    另外还可以设置参数满足条件时调用的方法触发,设置延时触发。

    • Returns
      设置返回结果。
    • Throws
      当一个方法调用时,抛出异常。
      例子:
            [Test]
            public void ShouldThrowExceptionOnMethodCall()
            {
                // Arrange 
                var foo = Mock.Create<IFoo>();
    
                Mock.Arrange(() => foo.Execute(string.Empty)).Throws<ArgumentException>();
    
                Assert.Throws<ArgumentException>(() => foo.Execute(string.Empty));
            }
    
    • ThrowsAsync
      调用异步方法时抛出异常。(官方文档有这个标记,貌似使用21912071版本时没有这个标记了)
    • When
      当满足条件时执行某操作。
      例子:
        public interface IFoo
        {
            bool IsCalled();
            string Prop { get; set; }
        }
    
            [Test]
            public void IsCalled_ShouldReturnTrue_WithMockedDependencies()
            {
                var foo = Mock.Create<IFoo>();
    
                Mock.Arrange(() => foo.Prop).Returns("test");//设置Prop返回"Test"
                Mock.Arrange(() => foo.IsCalled()).When(() => foo.Prop == "test").Returns(true);
    
                Assert.IsTrue(foo.IsCalled());
            }
    

    JustMock模拟属性

    上面的例子中已经用到Returns来模拟属性的值了,这里再看看还有其它的用法:

    • 根据索引模拟值
      假设一个类的属性是数组或者链表等,我们需要模拟其中某个索引下的值:
    //indexedFoo是一个数组
    Mock.Arrange(() => indexedFoo[1]).Returns("pong"); //Mock indexedFoo 下标1的值为“pong”
    
    • 设置属性
      我们需要验证一个属性是否被正确赋值时,可以用 ArrangeSet 来模拟设置属性并验证。
    [Test] 
        public void ShouldAssertPropertySet() 
        { 
            var foo = Mock.Create<IFoo>(); 
     
            Mock.ArrangeSet(() => foo.Value = 1); 
     
            foo.Value = 1; //执行赋值
     
            Mock.AssertSet(() => foo.Value = 1); 
        } 
    

    Matchers 匹配参数

    我们模拟有参数的方法时,要根据不同的参数设置不同的返回值,Matchers可以做到这些。

    1. Arg.AnyBool,Arg.AnyDouble,Arg.AnyFloat, Arg.AnyGuid, Arg.AnyInt, Arg.AnyLong, Arg.AnyObject, Arg.AnyShort, Arg.AnyString, Arg.NullOrEmpty
    2. Arg.IsAny()
    3. Arg.IsInRange([FromValue : int], [ToValue : int], [RangeKind])
    4. Arg.Matches(Expression<Predicate> expression)
    5. Arg.Ref()
    6. Args.Ignore()

    我们可以利用Arg.Matches<T>(Expression<Predicate<T>> expression) 生成我们想要的数据条件。

    Arg.Ref() 是C#里的ref 参数。

    Args.Ignore() 可以忽略参数,同标记方法IgnoreArguments()

    Matchers 不仅在构造参数中使用,还可以在Assert中使用:

        [Test] 
        public void ShouldUseMatchersInAssert() 
        { 
            var paymentService = Mock.Create<IPaymentService>(); 
     
            paymentService.ProcessPayment(DateTime.Now, 54.44M); 
     
            // 断言:不管DateTime什么时间,Payment amount都是54.44.
            Mock.Assert(() => paymentService.ProcessPayment( 
                Arg.IsAny<DateTime>(), 
                Arg.Matches<decimal>(paymentAmount => paymentAmount == 54.44M))); 
        } 
    

    Sequential Mocking 连续模拟

    Sequential mocking 允许我们多次执行代码返回不一样的值,具体代码理解:

    [TestMethod] 
        public void ShouldArrangeAndAssertInASequence() 
        { 
            var foo = Mock.Create<IFoo>(); 
            //只需要在Arrange()后面加上InSequence()
            Mock.Arrange(() => foo.GetIntValue()).Returns(0).InSequence(); 
            Mock.Arrange(() => foo.GetIntValue()).Returns(1).InSequence(); 
            Mock.Arrange(() => foo.GetIntValue()).Returns(2).InSequence(); 
     
            int actualFirstCall = foo.GetIntValue(); //结果是:0
            int actualSecondCall = foo.GetIntValue(); //结果是:1
            int actualThirdCall = foo.GetIntValue(); //结果是:2
            int actualFourthCall = foo.GetIntValue(); // 注意这是第四次调用,因为没有设置结果,实际上他应当是上次调用的结果:2
        }
    

    配合Matcher使用:

     Mock.Arrange(() => foo.Echo(Arg.Matches<int>(x => x > 10))).Returns(10).InSequence(); 
     Mock.Arrange(() => foo.Echo(Arg.Matches<int>(x => x > 20))).Returns(20).InSequence(); 
     
     int actualFirstCall = foo.Echo(11); //结果10
     int actualSecondCall = foo.Echo(21); //结果20
    

    还可以多次返回:

    Mock.Arrange(() => foo.Echo(Arg.AnyInt)).Returns(10).Returns(11).MustBeCalled(); 
     
    Assert.AreEqual(10, foo.Echo(1)); 
    Assert.AreEqual(11, foo.Echo(2)); 
    

    或者以数组方式返回:

    int[] returnValues = new int[3] { 1, 2, 3 }; 
     
    Mock.Arrange(() => foo.Echo(Arg.AnyInt)).ReturnsMany(returnValues); 
     
    var actualFirstCall = foo.Echo(10); // 结果:1
    var actualSecondCall = foo.Echo(10); // 结果:2
    var actualThirdCall = foo.Echo(10); // 结果:3
    

    Recursive Mocking 递归模拟

    递归Mock。有时候我们需要取值像这样的:foo.Bar.Baz.Do("x"),我们可以不用一个一个对象去模拟,我们只需要Mock 第一个,然后设置值就可以了。
    像这样:

     var foo = Mock.Create<IFoo>(); // mock 第一个对象
     
     Mock.Arrange(() => foo.Bar.Do("x")).Returns("xit"); //设置某次调用的值
     Mock.Arrange(() => foo.Bar.Baz.Do("y")).Returns("yit"); 
    

    最后

    这里涉及到的都是是JustMock的免费版功能。在工作中遇到一些问题,我们需要测试的目标方法中混有静态类提供的逻辑,免费的框架都不支持Mock静态类,需要用到付费版的高级功能。更好的时候,我们应该避免这些依赖,以方便我们测试。

  • 相关阅读:
    HDU Railroad (记忆化)
    HDU 1227 Fast Food
    HDU 3008 Warcraft
    asp vbscript 检测客户端浏览器和操作系统(也可以易于升级到ASP.NET)
    Csharp 讀取大文本文件數據到DataTable中,大批量插入到數據庫中
    csharp 在万年历中计算显示农历日子出错
    csharp create ICS file extension
    CSS DIV Shadow
    DataTable search keyword
    User select fontface/color/size/backgroundColor设置 字体,颜色,大小,背景色兼容主流浏览器
  • 原文地址:https://www.cnblogs.com/jimizhou/p/11424009.html
Copyright © 2020-2023  润新知