一、概念 Moq是利用諸如Linq表達式樹和Lambda表達式等·NET 3.5的特性,為·NET設計和開發的Mocking庫。Mock字面意思即模擬,模擬對象的行為已達到欺騙目標(待測試對象)的效果. Moq模擬類類型時,不可模擬密封類,不可模擬靜態方法(適配器可解決),被模擬的方法及屬性必須被v ...
一、概念
Moq是利用諸如Linq表達式樹和Lambda表達式等·NET 3.5的特性,為·NET設計和開發的Mocking庫。Mock字面意思即模擬,模擬對象的行為已達到欺騙目標(待測試對象)的效果.
Moq模擬類類型時,不可模擬密封類,不可模擬靜態方法(適配器可解決),被模擬的方法及屬性必須被virtual修飾.
二、示例
1 //待模擬對象 2 public interface ITaxCalculate 3 { 4 decimal GetTax(decimal rawPrice); 5 } 6 7 public class Product 8 { 9 public int Id { get; set; } 10 11 public string Name { get; set; } 12 13 public decimal RawPrice { get; set; } 14 15 //目標方法 16 public decimal GetPriceWithTax(ITaxCalculate calc) 17 { 18 return calc.GetTax(RawPrice) + RawPrice; 19 } 20 } 21 22 //單元測試 23 [TestMethod] 24 public void TestGetTax() 25 { 26 Product product = new Product 27 { 28 Id = 1, 29 Name = "TV", 30 RawPrice = 25.0M 31 }; 32 33 //創建Mock對象,反射構建模擬對象空框架 34 Mock<ITaxCalculate> fakeTaxCalculator = new Mock<ITaxCalculate>(); 35 36 //模擬對象行為 37 fakeTaxCalculator.Setup(tax => tax.GetTax(25.0M)).Returns(5.0M); 38 39 //調用目標方法 40 decimal calcTax = product.GetPriceWithTax(fakeTaxCalculator.Object); 41 42 //斷言 43 Assert.AreEqual(calcTax, 30.0M); 44 }
三、Mock方法
- Mock構造方法
Mock構造方法主要存在兩種重載,無參以及傳入參數MockBehavior,Mock預設行為:MockBehavior.Loose.
MockBehavior.Strict:對象行為未設置時調用拋出異常,示例如下.
MockBehavior.Loose:對象行為未設置時調用不拋出異常,如有必要返回控制,如:0,null.
MockBehavior.Default:等同於Loose.
1 //構造方法 2 public Mock(); 3 public Mock(MockBehavior behavior); 4 5 //Strict示例 6 Mock<IOrder> order = new Mock<IOrder>(MockBehavior.Strict); 7 order.Object.ShowTitle(string.Empty);
- MockFactory
Mock工廠,構建MockFactory時傳入MockBehavior,通過Create方法創建Mock,次方法類似Mock構造方法.
1 MockFactory factory = new MockFactory(MockBehavior.Loose); 2 Mock<IOrder> order = factory.Create<IOrder>();
- Setup
模擬對象行為方法,模擬出的方法與原有業務無關
1 //模擬介面 2 Mock<ICustomer> icustomer = new Mock<ICustomer>(); 3 //模擬普通方法 4 icustomer.Setup(p => p.AddCall()); 5 icustomer.Setup(p => p.GetCall("Tom")).Returns("Hello"); 6 7 //模擬含有引用、輸出參數方法 8 string outString = "00"; 9 icustomer.Setup(p => p.GetAddress("", out outString)).Returns("sz"); 10 icustomer.Setup(p => p.GetFamilyCall(ref outString)).Returns("xx"); 11 12 //模擬有返回值方法 13 icustomer.Setup(p => p.GetCall(It.IsAny<string>())).Returns((string s) => "Hello " + s); 14 15 //模擬類 16 Mock<Customer> customer = new Mock<Customer>(); 17 //模擬屬性 18 customer.Setup(p => p.Name).Returns("Tom"); 19 Assert.AreEqual("Tom", customer.Object.Name); 20 21 //另一種方法模擬屬性 22 customer.SetupProperty(p => p.Name, "Tom2"); 23 Assert.AreEqual("Tom2", customer.Object.Name); 24 25 //模擬類方法 26 customer.Setup(p => p.GetNameById(1)).Returns("2"); 27 Assert.AreEqual("2", customer.Object.GetNameById(1));
It用於添加參數約束,它有以下幾個方法:
Is<T>:匹配給定符合規則的值
IsAny<T>:匹配給定類型的任何值
IsRegex<T>:正則匹配
IsInRange<T>:匹配給定類型的範圍1 //對同一個動作可以模擬多個行為,執行動作時,從後往前依次匹配,直到匹配到為止 2 var customer = new Mock<ICustomer>(); 3 customer.Setup(p => p.SelfMatch(It.IsAny<int>())).Returns((int k) => "任何數" + k); 4 Console.WriteLine(customer.Object.SelfMatch(100)); 5 6 customer.Setup(p => p.SelfMatch(It.Is<int>(i => i % 2 == 0))).Returns("偶數"); 7 Console.WriteLine(customer.Object.SelfMatch(6)); 8 9 customer.Setup(p => p.SelfMatch(It.IsInRange<int>(0, 10, Range.Inclusive))).Returns("10以內的數"); 10 Console.WriteLine(customer.Object.SelfMatch(8)); 11 12 Console.WriteLine(customer.Object.SelfMatch(18)); 13 Console.WriteLine(customer.Object.SelfMatch(99)); 14 15 customer.Setup(p => p.ShowException(It.IsRegex(@"^\d+$"))).Throws(new Exception("不能是數字")); 16 customer.Object.ShowException("e1");
- Callback
該方法用於模擬方法執行後回調執行,配合Setup使用
1 Mock<ICustomer> customer = new Mock<ICustomer>(); 2 customer.Setup(p => p.GetCall(It.IsAny<string>())) 3 .Returns("方法調用") 4 .Callback((string s) => Console.WriteLine("OK " + s)); 5 customer.Object.GetCall("x");
- Throws
拋出異常,配合Setup使用
1 Mock<ICustomer> customer = new Mock<ICustomer>(); 2 customer.Setup(p => p.ShowException(string.Empty)).Throws(new Exception("參數不能為空!")); 3 customer.Object.ShowException("");
- Verify、VerifyAll
驗證模擬的方法是否被執行。示例中可通過Verify驗證模擬的tax.GetTax(25.0M)是否在Product中被執行
1 //Verifiable標記 2 Mock<ICustomer> customer = new Mock<ICustomer>(); 3 customer.Setup(p => p.GetCall(It.IsAny<string>())).Returns("方法調用").Verifiable(); 4 //若不執行此句代碼則驗證失敗 5 customer.Object.GetCall(""); 6 customer.Verify(); 7 8 //Verify驗證 9 customer.Setup(p => p.GetCall()); 10 customer.Object.GetCall(); 11 //Verify方法表明該動作一定要在驗證之前執行,若調用verify之前都沒執行則拋出異常 12 customer.Verify(p => p.GetCall()); 13 14 //添加次數驗證 15 customer.Object.GetCall(); 16 customer.Verify(p => p.GetCall(), Times.AtLeast(2), "至少應被調用2次"); 17 18 //驗證所有被模擬的動作是否都被執行,無論是否標記為Verifiable 19 customer.VerifyAll();
- As
向Mock中條件一個介面實現,只能在對象的屬性、方法首次使用之前使用,且參數只能是介面,否則拋出異常。
之前一直不知道As方法存在有什麼意義,雖然調試時監測對象信息可以看到兩個Mock之間關聯的痕跡,但是一直不知道有什麼用,該怎麼用,直到今天看到一篇博客...
1 public interface IFirstInterface 2 { 3 int SomeMethodOnFirstInterface(); 4 } 5 6 public interface ISecondInterface 7 { 8 int SomeMethodOnSecondInterface(); 9 } 10 11 public interface SomeClassImplementingInterfaces : IFirstInterface, ISecondInterface 12 { 13 } 14 15 public class SomeClass 16 { 17 public static int MultipleInterfaceUser<T>(T x) 18 where T : IFirstInterface, ISecondInterface 19 { 20 IFirstInterface f = (IFirstInterface)x; 21 ISecondInterface s = (ISecondInterface)x; 22 23 return f.SomeMethodOnFirstInterface() + s.SomeMethodOnSecondInterface(); 24 } 25 } 26 27 //測試代碼 28 Mock<SomeClassImplementingInterfaces> c = new Mock<SomeClassImplementingInterfaces>(); 29 30 Mock<IFirstInterface> firstMock = c.As<IFirstInterface>(); 31 firstMock.Setup(m => m.SomeMethodOnFirstInterface()).Returns(2); 32 33 Mock<ISecondInterface> secondMock = firstMock.As<ISecondInterface>(); 34 secondMock.Setup(m => m.SomeMethodOnSecondInterface()).Returns(4); 35 36 int returnValue = SomeClass.MultipleInterfaceUser<SomeClassImplementingInterfaces>(c.Object); 37 38 Assert.AreEqual(returnValue, 6);
四、參考鏈接
- http://blog.csdn.net/alicehyxx/article/details/50667307
- http://www.cnblogs.com/wintersun/archive/2010/09/04/1818092.html