.net core 單元測試之 JustMock第二篇

  • 2019 年 10 月 3 日
  • 筆記

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靜態類,需要用到付費版的高級功能。更好的時候,我們應該避免這些依賴,以方便我們測試。

Tags: