一、概念
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