using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Moq; using Moq.Internals; using Moq.Language; using Moq.Protected; using Xunit; using MoqStudy.MockModel; namespace MoqStudy.Test { /// /// Moq官网快速入门示例 学习 /// https://github.com/Moq/moq4/wiki/Quickstart /// public class MoqDemoTest:IDisposable { //说明:因为是个人学习用,所有注释比较多 #region xUnit 初始准备 Mock mock = new Mock(); IFoo ifoo; delegate void SubmitCallback(ref Bar bar); public string IsLarge() { return Match.Create(s => !String.IsNullOrEmpty(s) && s.Length > 10); } public MoqDemoTest() { ifoo = mock.Object; } #endregion #region 设置方法 /// /// 模拟方法 /// [Fact] public void Method_Test() { mock //参数为"ping"时,DoSomething 返回 true .Setup(foo => foo.DoSomething("ping")) .Returns(true); Assert.True(ifoo.DoSomething("ping")); Assert.False(ifoo.DoSomething("sssss")); } /// /// 输出(out)参数 /// [Fact] public void Method_OutArgument_Test() { //输出参数 var outString = "ack"; mock //TryParse 返回 true,输出参数 outString 被赋值为 "ACK" //调用方法之后,outString保持原值,方法并不会改变输出能数的值 .Setup(foo => foo.TryParse("ping", out outString)) .Returns(true); Assert.True(mock.Object.TryParse("ping", out outString)); Assert.Equal("ack", outString); //调用方法,并不会改变输出参数的值,即便是方法不符合设置。 Assert.False(mock.Object.TryParse("xyz", out outString)); Assert.Equal("ack", outString); } /// /// 引用(ref)参数 /// [Fact] public void Method_RefArgument_Test() { var instance = new Bar(); mock //只有当调用方法的ref参数与设置时ref参数是相同实例时才匹配。 .Setup(foo => foo.Submit(ref instance)) .Returns(true); //调用参数与设置参数是同一个实例,匹配 Assert.True(ifoo.Submit(ref instance)); //调用方法的ref参数与设置时ref参数,不是同一个实例,不匹配 var notSame = new Bar(); Assert.False(ifoo.Submit(ref notSame)); } /// /// 设置返回值 /// [Fact] public void Method_Return_Test() { mock //设置返回值 .Setup(x => x.DoSomethingStringy(It.IsAny())) .Returns("ack"); Assert.Equal("ack", ifoo.DoSomethingStringy("ABC")); Assert.Equal("ack", ifoo.DoSomethingStringy("123")); Assert.Equal("ack", ifoo.DoSomethingStringy("Abc123_/+")); } /// /// 返回值中使用参数 /// [Fact] public void Method_Return_UseArgument_Test() { mock //在返回值中,使用方法参数 .Setup(x => x.DoSomethingStringy(It.IsAny())) //返回参数的小写形式 .Returns((string s) => s.ToLower()); Assert.Equal("ab", ifoo.DoSomethingStringy("AB")); Assert.Equal("ab", ifoo.DoSomethingStringy("aB")); Assert.Equal("ab", ifoo.DoSomethingStringy("ab")); } /// /// 返回值中使用多参数 /// [Fact] public void Method_Return_UseMultiArgument_Test() { mock //在返回值中,使用多方法参数 .Setup(x => x.DoSomething(It.IsAny(), It.IsAny())) //返回,两参数字符值是否相等 .Returns((int v,string s) => v.ToString() == s.ToLower()); Assert.True(ifoo.DoSomething(11,"11")); Assert.False(ifoo.DoSomething(11,"aa")); Assert.False(ifoo.DoSomething(11,"110")); } /// /// 调用特定参数时抛出异常 /// [Fact] public void Method_Throwing_Test() { mock.Setup(foo => foo.DoSomething("reset")).Throws(); mock.Setup(foo => foo.DoSomething("")).Throws(new ArgumentException("command")); //指定值时,抛出异常 Assert.Throws(() => { ifoo.DoSomething("reset"); }); Assert.Throws(() => { ifoo.DoSomething(""); }); //非指定值时(loose模式时),不抛出异常 Assert.False(ifoo.DoSomething("not reset")); } /// /// lazy evaluating return value /// 设置返回值,延迟求值 /// [Fact] public void Method_Return_Lazy_Test() { var count = 1; mock //返回值设置为踉踪变量,变量值变化,返回值踉着变化 .Setup(foo => foo.GetCount()) //返回值踉踪变量count,count值变化,返回值踉着变化 .Returns(() => count); //变量值变为3,则返回值踉着变为3 count = 3; Assert.Equal(3, ifoo.GetCount()); //变量值变为5,则返回值踉着变为5 count = 5; Assert.Equal(5, ifoo.GetCount()); } /// /// 每次调用方法,返回不同的值 /// 实质为:调用Callback(),改变返回值引用的变量值 /// 另一种方法,见:Miscellaneous_Different_Test()方法 /// [Fact] public void Method_Return_Different_Test() { var calls = 0; mock .Setup(foo => foo.GetCount()) //返回值设置为:踉踪变量 .Returns(() => calls) //调用方法,改变踉踪变量的值 .Callback(() => calls++); Assert.Equal(0, ifoo.GetCount()); Assert.Equal(1, ifoo.GetCount()); Assert.Equal(2, ifoo.GetCount()); Assert.Equal(3, ifoo.GetCount()); Assert.Equal(4, ifoo.GetCount()); } /// /// 重复设置 /// 后面的设置会覆盖前面的设置 /// [Fact] public void Method_RepeatSet_Test() { mock.//首次设置 Setup(f => f.DoSomething("ping")) .Returns(true); Assert.True(ifoo.DoSomething("ping")); mock.//重复设置相同参数 Setup(f => f.DoSomething("ping")) .Returns(false); //后面的设置,覆盖前面的设置 Assert.False(ifoo.DoSomething("ping")); } /// /// 多次设置同一参数不同值的返回值 /// 保持多次设置 /// [Fact] public void Method_MultiSet_Test() { mock.//设置参数为"ping" Setup(f => f.DoSomething("ping")) .Returns(true); mock.//设置参数为"xyz" Setup(f => f.DoSomething("xyz")) .Returns(true); Assert.True(ifoo.DoSomething("ping")); Assert.True(ifoo.DoSomething("xyz")); //其它均为false Assert.False(ifoo.DoSomething("abc1232")); } #endregion #region 匹配参数 /// /// 匹配参数的任意值 /// [Fact] public void Argument_IsAny_Test() { mock //匹配参数的任意值 .Setup(foo => foo.DoSomething(It.IsAny())) .Returns(true); Assert.True(ifoo.DoSomething(null)); Assert.True(ifoo.DoSomething("")); Assert.True(ifoo.DoSomething("ping")); Assert.True(ifoo.DoSomething("ABCde123456")); Assert.True(ifoo.DoSomething(" +=-$%)()")); } /// /// 匹配"ref"的任意值(Moq需要4.8及更高版本) /// [Fact] public void Argument_Ref_IsAny_Test() { mock .Setup(foo => foo.Submit(ref It.Ref.IsAny)) .Returns(true); Bar bar1 = null; Bar bar2 = new Bar(); Bar bar3 = new Bar() { Baz = null }; Bar bar4 = new Bar() { Baz = new Baz() { Name="wanggaofeng"} }; Assert.True(ifoo.Submit(ref bar1)); Assert.True(ifoo.Submit(ref bar2)); Assert.True(ifoo.Submit(ref bar3)); Assert.True(ifoo.Submit(ref bar4)); } /// /// 匹配符合Func<>的任意值 /// [Fact] public void Argument_Is_Test() { mock //参数匹配 Fun<>() .Setup(foo => foo.Add(It.Is(i => i % 2 == 0))) .Returns(true); Assert.True(ifoo.Add(2)); Assert.True(ifoo.Add(4)); Assert.True(ifoo.Add(6)); Assert.False(ifoo.Add(1)); Assert.False(ifoo.Add(3)); Assert.False(ifoo.Add(5)); } /// /// 匹配指定范围 /// [Fact] public void Argument_Range_Test() { mock //参数匹配指定范围,注意边界值 .Setup(foo => foo.Add ( //Range.Inclusive 排除边界值 //Range.Exclusive 包括边界值 It.IsInRange(0, 10, Range.Inclusive)) ) .Returns(true); Assert.True(ifoo.Add(0)); Assert.True(ifoo.Add(5)); Assert.True(ifoo.Add(10)); Assert.False(ifoo.Add(-1)); Assert.False(ifoo.Add(11)); Assert.False(ifoo.Add(10000)); } /// /// 匹配正则表达式 /// [Fact] public void Agument_Regex_Test() { mock //参数匹配指定正则表达式 .Setup(x => x.DoSomethingStringy ( //指定正则及正则选项 It.IsRegex("[a-d]+", System.Text.RegularExpressions.RegexOptions.IgnoreCase)) ) .Returns("foo"); Assert.Equal("foo", ifoo.DoSomethingStringy("abcdaaacccddddd")); Assert.NotEqual("foo", ifoo.DoSomethingStringy("123456")); } #endregion #region 设置属性 /// /// 设置属性 /// [Fact] public void Property_Setup_Test() { mock //设置属性值 .Setup(f => f.Name) .Returns("wanggaofeng"); Assert.Equal("wanggaofeng", ifoo.Name); } /// /// 自动模拟多层属性 /// (递归模拟属性) /// [Fact] public void Property_Setup_Recursive_Test() { mock //自动模拟多层(即递归Mocks) .Setup(f => f.Bar.Baz.Name) .Returns("Bar.Baz.Name"); Assert.Equal("Bar.Baz.Name", ifoo.Bar.Baz.Name); } /// /// 验证属性设置 /// [Fact] public void Property_VerifySet_Test() { MockException verifyException = null; //不执行,为属性赋指定的值,则验证出现异常 try { //检查代码 foo.Name = "foo" 已经执行过 mock.VerifySet(foo => foo.Name = "foo"); verifyException = null; } catch (MockException ex) { verifyException = ex; } //验证发生异常 Assert.NotNull(verifyException); //为属性赋指定的值,验证通过 try { mock //设置属性(loose模式下,可以不设置) .Setup(f => f.Name) .Returns("bbb"); //执行赋值语句 ifoo.Name = "foo"; //检查代码 foo.Name = "foo" 已经执行过 mock.VerifySet(foo => foo.Name = "foo"); verifyException = null; } catch (MockException ex) { verifyException = ex; } Assert.Equal("bbb", ifoo.Name); Assert.Null(verifyException); } /// /// 期望属性被赋值为指定的值 /// 可由VerifySet替代 /// [Fact] public void Property_SetupSet_Test() { mock //期望属性被赋值为指定的值 //可以使用 VerifySet 替代 .SetupSet(foo => foo.Name = "ping") //使设置可以验证,如果没有此设置,则SetupSet设置,无从知道SetupSet设置的效果 .Verifiable(); //属性值不为指定的值时,验证时异常 Assert.Throws(()=>mock.Verify()); //属性值赋值为指定值时,验证通过 //或者 加上 mock.Setup(f => f.Name).Returns("ping"); ifoo.Name = "ping"; mock.Verify(); } /// /// 验证属性读取 /// [Fact] public void Property_SetupGet_Test() { mock //期望属性值被读取 //可以使用 VerifyGet 替代 .SetupGet(foo => foo.Name) //使设置可以验证,如果没有此设置,则SetupGet设置,无从知道SetupSet设置的效果 .Verifiable(); //属性值未被读取时,验证时异常 Assert.Throws(() => mock.Verify()); //属性值被读取时,验证通过 //或者加上 mock.Setup(f => f.Name).Returns("ping"); var getName = ifoo.Name; mock.Verify(); } /// /// 设置后,改变属性值,再次调用时,属性值不变 /// [Fact] public void Property_Setup_Change_Test() { mock.Setup(f => f.Name) .Returns("ping"); Assert.Equal("ping", ifoo.Name); //为属性赋新值 ifoo.Name = "ack"; //再次调用,属性值保持"ping"不变 Assert.False(ifoo.Name == "ack"); Assert.True(ifoo.Name == "ping"); } /// /// 重复设置属性 /// 后面设置的属性值,覆盖前面的设置 /// [Fact] public void Property_Repeat_Setup_Test() { mock //设置属性值为 ping .Setup(f => f.Name) .Returns("ping"); Assert.Equal("ping", ifoo.Name); mock //再次设置属性值为 ack .Setup(f => f.Name) .Returns("ack"); Assert.Equal("ack", ifoo.Name); } /// /// 存根设置的属性值 /// 设置后自动开始跟踪属性值,为属性赋值后,调用属性值踉着改变 /// [Fact] public void Property_SetupProperty_Test() { //注意对比Setup设置:Setup设置的属性,为属性赋新值后,调用属性值保持不变 // 设置并开始“跟踪”此属性 mock.SetupProperty(f => f.Name); // 或者 设置默认值并开始“跟踪”此属性 mock.SetupProperty(f => f.Name, "foo"); // 初始值被存储 Assert.Equal("foo", ifoo.Name); // 设定新值改,初始值被新值覆盖 ifoo.Name = "bar"; Assert.Equal("bar", ifoo.Name); } /// /// 存根所有模拟属性 /// [Fact] public void Property_SetupAllProperties_Test() { mock //存根所有属性,这意味着设置其值将导致它被保存并在请求属性时返回。 //每个属性值设置为Mock框架的默认值。 .SetupAllProperties(); Assert.Null(ifoo.Name); //赋新值 ifoo.Name = "change"; Assert.Equal("change", ifoo.Name); } #endregion #region 设置事件(暂时省略) //注册一个事件 [Fact(Skip = "暂时省略")] public void Event_Raise_Test() { Assert.True(false, "暂时略过,以后学习"); } #endregion #region 回调函数 /// /// 设置回调函数 /// [Fact] public void Callback_Test() { var calls = 0; mock //回调函数,返回后,执行其它代码 .Setup(foo => foo.DoSomething("ping")) .Returns(true) .Callback(() => calls++); //方法执行成功,执行回调 Assert.True(ifoo.DoSomething("ping")); Assert.Equal(1, calls); //方法执行成功,再次执行回调 Assert.True(ifoo.DoSomething("ping")); Assert.Equal(2, calls); //方法执行失败,不执行回调 Assert.False(ifoo.DoSomething("xyz")); Assert.Equal(2, calls); } /// /// 回调函数中使用设置参数 /// [Fact] public void Callback_Argument_Test() { var callArgs = new List(); mock.Setup(foo => foo.DoSomething(It.IsAny())) .Returns(true) .Callback((string s) => callArgs.Add(s)); // 或者如下:回调函数的泛型版本 // .Callback(s => callArgs.Add(s)); Assert.True(ifoo.DoSomething("first")); Assert.Single(callArgs); Assert.Contains("first", callArgs); Assert.True(ifoo.DoSomething("second")); Assert.Equal(2, callArgs.Count); Assert.Contains("second", callArgs); } /// /// 泛型回调函数中使用参数 /// [Fact] public void Callback_Generic_Argument_Test() { var callArgs = new List(); mock.Setup(foo => foo.DoSomething(It.IsAny())) .Returns(true) .Callback(s => callArgs.Add(s)); Assert.True(ifoo.DoSomething("first")); Assert.Single(callArgs); Assert.Contains("first", callArgs); Assert.True(ifoo.DoSomething("second")); Assert.Equal(2, callArgs.Count); Assert.Contains("second", callArgs); } /// /// 回调函数中使用多个参数 /// [Fact] public void Callback_MultiArgument_Test() { var callCount = 0; var callArgs = new List(); mock.Setup(foo => foo.DoSomething(It.IsAny(), It.IsAny())) .Returns(true) .Callback ( (i, s) => { callCount += 1; callArgs.Add(s); } ); Assert.True(ifoo.DoSomething(99, "first")); Assert.Equal(1, callCount); Assert.Contains("first", callArgs); Assert.True(ifoo.DoSomething(88, "second")); Assert.Equal(2, callCount); Assert.Contains("second", callArgs); } /// /// 指定回调函数位置:前置执行或后置执行 /// [Fact] public void Callback_BeforeAndAfter_Test() { var returnBefore = new List(); var returnAfter = new List(); //此验证并不严谨. 控制台程序已经另行验证过。 // 也可以在xUnit中打印信息验证 mock.Setup(foo => foo.DoSomething(It.IsAny())) .Callback(s => returnBefore.Add(s)) .Returns(true) .Callback(s => returnAfter.Add(s)); Assert.True(ifoo.DoSomething("xyz")); Assert.Contains("xyz", returnBefore); Assert.Contains("xyz", returnAfter); } /// /// 回调函数中使用 re/out 参数 /// [Fact] public void Callback_RefAndOut_Parameter_Test() { mock //回调函数中使用 re/out 参数 .Setup(foo => foo.Submit(ref It.Ref.IsAny)) .Callback ( new SubmitCallback ( (ref Bar bar) => { bar.Baz.Name = "xyz"; } ) ); var myBar = new Bar() { Baz = new Baz() { Name = "gaofeng" } }; mock.Object.Submit(ref myBar); Assert.Equal("xyz", myBar.Baz.Name); //和没有回调函数的对比验证 var mock2 = new Mock(); mock2.Setup(foo => foo.Submit(ref It.Ref.IsAny)); var myBar2 = new Bar() { Baz = new Baz() { Name = "gaofeng" } }; mock2.Object.Submit(ref myBar2); Assert.Equal("gaofeng", myBar2.Baz.Name); } #endregion #region 验证 /// /// 验证匹配项是否调用 /// 不设置,验证出现异常 /// [Fact] public void Verify_NoSetup_Test() { //验证匹配项是否调用 Assert.Throws(() => mock.Verify(f => f.Add(1))); } /// /// 验证匹配项是否调用 /// 设置后不运行,验证出现异常 /// [Fact] public void Verify_Setup_NoRun_Test() { mock //验证匹配项是否调用 .Setup(f => f.Add(2)) .Returns(true); Assert.Throws(() => { mock.Verify(f => f.Add(2)); }); } /// /// 验证匹配项是否调用 /// 设置并调用后,验证通过 /// [Fact] public void Verify_Setup_Test() { mock //设置 .Setup(f => f.Add(2)) .Returns(true); //调用运行 ifoo.Add(2); //通过验证 mock.Verify(f => f.Add(2)); } /// /// 验证失败时,自定义异常信息 /// [Fact] public void Verify_ErrorMessage_Test() { MockException mockException = null; try { mock.Verify(foo => foo.DoSomething("ping"), "Custom exception message"); mockException = null; } catch (MockException ex) { mockException = ex; } Assert.StartsWith("Custom exception message", mockException.Message); } /// /// 验证方法从未被调用 /// [Fact] public void Verify_Never_Test() { mock.Verify(foo => foo.DoSomething("ping"), Times.Never()); ifoo.DoSomething("ping"); Assert.Throws(()=> { mock.Verify(foo => foo.DoSomething("ping"), Times.Never()); }); } /// /// 验证至少调用过一次 /// [Fact] public void Verify_AtLeastOnce_Test() { MockException mockException = null; var customInfo = "custom info"; try { mock.Verify(foo => foo.DoSomething("ping"), Times.AtLeastOnce(), customInfo); } catch(MockException ex) { mockException = ex; } Assert.NotNull(mockException); Assert.StartsWith(customInfo, mockException.Message); //验证通过 mock.Setup(f => f.DoSomething("ping")).Returns(true); try { mock.Object.DoSomething("ping"); mock.Verify(foo => foo.DoSomething("ping"), Times.AtLeastOnce(), customInfo); mockException = null; } catch (MockException ex) { mockException = ex; } Assert.Null(mockException); } /// /// 验证属性被读取过 /// [Fact] public void Verify_Propertie_Get_Test() { MockException mockException = null; var customInfo = "custom info"; try { mock.VerifyGet(foo => foo.Name, customInfo); } catch (MockException ex) { mockException = ex; } Assert.NotNull(mockException); Assert.StartsWith(customInfo, mockException.Message); //验证通过情况 mock.SetupGet(f => f.Name); //或者 //mock.SetupProperty(f => f.Name); try { //读取1次属性 var getName = ifoo.Name; mock.VerifyGet(foo => foo.Name, customInfo); mockException = null; } catch (MockException ex) { mockException = ex; } Assert.Null(mockException); } /// /// 验证对属性的赋值(已弃用的方法) /// [Fact] public void Verify_Propertie_Set_Test() { MockException mockException = null; var customInfo = "custom info"; try { mock.VerifySet(foo => foo.Name, customInfo); } catch (MockException ex) { mockException = ex; } Assert.NotNull(mockException); Assert.StartsWith(customInfo, mockException.Message); //验证通过情况 mock.SetupSet(f => f.Name); //或者 //mock.SetupProperty(f => f.Name); try { ifoo.Name = "zhangsan"; mock.VerifySet(foo => foo.Name, customInfo); mockException = null; } catch (MockException ex) { mockException = ex; } Assert.Null(mockException); } /// /// 验证对于属性设置特定的值 /// [Fact] public void Verify_PropertieValue_Test() { MockException mockException = null; var customInfo = "custom info"; try { mock.VerifyGet(foo => foo.Name, customInfo); } catch (MockException ex) { mockException = ex; } Assert.NotNull(mockException); Assert.StartsWith(customInfo, mockException.Message); //验证通过情况 mock.SetupProperty(f => f.Name, "wanggaofeng"); try { var name = mock.Object.Name; mock.VerifyGet(foo => foo.Name, customInfo); mockException = null; } catch (MockException ex) { mockException = ex; } Assert.Null(mockException); } /// /// 验证匹配的参数 /// [Fact] public void Verify_Propertie_Argument_Test() { MockException mockException = null; var customInfo = "custom info"; try { mock.VerifySet(foo => foo.Value = It.IsInRange(1, 5, Range.Inclusive), customInfo); } catch (MockException ex) { mockException = ex; } Assert.NotNull(mockException); Assert.StartsWith(customInfo, mockException.Message); //验证通过情况 mock.SetupProperty(f => f.Value, 3); try { //赋值一次 mock.Object.Value = 4; mock.VerifySet(foo => foo.Value = It.IsInRange(1, 5, Range.Inclusive), customInfo); mockException = null; } catch (MockException ex) { mockException = ex; } Assert.Null(mockException); } #endregion #region 定制模拟行为 /// /// 定制Mock模式 /// 默认模式,等同宽松模式:未设置的Mock行为或属性保持默认值或默认行为 /// 严格模式:未设置的Mock行为或属性,不可使用(异常) /// [Fact] public void CustomMock_Behavior_Test() { //默认模式,等同宽松模式 var looseOrDefaultMock = new Mock(MockBehavior.Loose); Assert.Null(looseOrDefaultMock.Object.Name); Assert.False(looseOrDefaultMock.Object.Add(1)); //严格模式 var strictMock = new Mock(MockBehavior.Strict); Assert.Throws(()=> { var temp = strictMock.Object.Name; }); Assert.Throws(()=> { strictMock.Object.Add(1); }); } /// /// 使用Callbase,调用真实对象的实际方法或属性 /// [Fact] public void CustomMock_Callbase_Test() { var mock = new Mock { CallBase = true }; var actual = mock.Object; mock //参数长度大于5,调用真实代码,返回参数的大写形式 .Setup(m => m.NameFormat(It.Is(f => f.Length > 5))) .CallBase(); mock //参数长度小于等于5,模拟返回值,返回参数的小写形式 .Setup(m => m.NameFormat(It.Is(f => f.Length <= 5))) .Returns(arg=>arg.ToLower()); var length3 = "AbC"; var length4 = "Abcd"; var length5 = "aBced"; var length6 = "Acccde"; var length10 = "aBcdefghig"; Assert.Equal(length3.ToLower(), actual.NameFormat(length3)); Assert.Equal(length4.ToLower(), actual.NameFormat(length4)); Assert.Equal(length5.ToLower(), actual.NameFormat(length5)); Assert.Equal(length6.ToUpper(), actual.NameFormat(length6)); Assert.Equal(length10.ToUpper(), actual.NameFormat(length10)); } /// /// 创造自动递归的Mock /// Mock对象对于它的任何成员将会返回一个新的 Mock 对象。 /// [Fact] public void CustomMock_Automatic_Test() { var mock = new Mock { DefaultValue = DefaultValue.Mock }; // default is DefaultValue.Empty //默认是 DefaultValue.Empty // this property access would return a new mock of Bar as it's "mock-able" // 现在这个属性将会返回一个新的 Mock 对象 Bar value = mock.Object.Bar; Assert.NotNull(value); // the returned mock is reused, so further accesses to the property return the same mock instance. this allows us to also use this instance to set further expectations on it if we want // 可以使用返回的 Mock 对象, 后即对属性的访问返回相同的对象实例,这就允许我们可以进行后继的设置 //再次转为Mock类型 var barMock = Mock.Get(value); barMock.Setup(b => b.Submit()).Returns(true); } /// /// 中心化的 Mock 实例创建和管理 /// 你可以在一个地方使用 MockRepository 创建和验证所有的 Mock 对象,设置 MockBehavior, CallBse 和 DefaultValue 约束。 /// [Fact] public void CustomMock_MockRepository_Test() { var repository = new MockRepository(MockBehavior.Strict) { DefaultValue = DefaultValue.Mock, CallBase = false, }; // Create a mock using the repository settings // 使用中心化设置创建 Mock 对象 var fooMock = repository.Create(); // Create a mock overriding the repository settings // 创建覆盖中心化设置的 Mock 对象 var barMock = repository.Create(MockBehavior.Loose); // Verify all verifiable expectations on all mocks created through the repository // 验证 try { repository.Verify(); Assert.True(true); } catch(Exception ex) { Assert.Null(ex); } } #endregion #region 其它 /// /// Miscellaneous /// 按调用顺序返回不同的值,超过次数报异常 /// [Fact] public void Miscellaneous_Different_Test() { var mock = new Mock(); var ifoo = mock.Object; mock.SetupSequence(f => f.GetCount()) .Returns(3) // 第1次调用,返回3 .Returns(2) // 第2次调用,返回2 .Returns(1) // 第3次调用,返回1 .Returns(0) // 第4次调用,返回0 .Throws(new InvalidOperationException()); // 第5及后续调用,异常 Assert.Equal(3, ifoo.GetCount()); Assert.Equal(2, ifoo.GetCount()); Assert.Equal(1, ifoo.GetCount()); Assert.Equal(0, ifoo.GetCount()); Assert.Throws(() => ifoo.GetCount()); } /// /// 设置受保护成员的期望值 /// (无法获得这些成员的智能感知,因此使用成员名作为字符串访问它们) /// [Fact] public void Miscellaneous_Protected_Test() { var mock = new Mock() { CallBase = true }; var protectValue = 5; mock.Protected() .Setup("Doubling", It.IsAny()) .Returns(protectValue); // 如果用到了参数匹配, 必须使用 ItExpr 来代替 It // 以后计划改进 mock.Protected() .Setup("Execute", ItExpr.IsAny()) .Returns(true); //注意使用方法: //设置模拟对象的保护成员后,直接访问不了(保护级别限制) // 使用验证方法进行验证,或者在其它设置里直接使用保护方式的变量结果。 mock.Setup(t => t.CompareWithDoubling(It.IsAny())) .Returns((decimal p)=> { return p > protectValue; }); var runValue1 = mock.Object.CompareWithDoubling(1); var runValue3 = mock.Object.CompareWithDoubling(3); var runValue5 = mock.Object.CompareWithDoubling(5); var runValue7 = mock.Object.CompareWithDoubling(7); Assert.False(runValue1); Assert.False(runValue3); Assert.False(runValue5); Assert.True(runValue7); } /// /// 接口方式,设置保护成员 /// 4.8及更高版本 /// [Fact] public void Miscellaneous_Protected_UseInterface_Test() { var mock = new Mock() { CallBase = true }; var protectValue = 5; mock //保护方法转为接口,进行设置 .Protected() .As() .Setup(m=>m.Doubling(It.IsAny())) .Returns(protectValue); //注意使用方法: //设置模拟对象的保护成员后,直接访问不了(保护级别限制) // 使用验证方法进行验证,或者在其它设置里直接使用保护方式的变量结果。 mock.Setup(t => t.CompareWithDoubling(It.IsAny())) .Returns((decimal p) => { return p > protectValue; }); var runValue1 = mock.Object.CompareWithDoubling(1); var runValue3 = mock.Object.CompareWithDoubling(3); var runValue5 = mock.Object.CompareWithDoubling(5); var runValue7 = mock.Object.CompareWithDoubling(7); Assert.False(runValue1); Assert.False(runValue3); Assert.False(runValue5); Assert.True(runValue7); } #endregion #region 高级特性 /// /// 从 Mock 实例重新获得 Mock 对象 /// [Fact] public void Advanced_GetMockFromMocked_Test() { // 获取模拟实例 IFoo foo =new Mock().Object; //再次转换成模拟对象 var fooMock = Mock.Get(foo); //进行设置 fooMock.Setup(f => f.GetCount()).Returns(42); //使用 var getCount = fooMock.Object.GetCount(); //断言 Assert.Equal(42,getCount); } /// /// 在模拟中实现多个接口 /// [Fact] public void Advanced_MulttipleInteface_Test() { var mockDemo = new Mock(); var mockInterface = mockDemo.As(); mockInterface.Setup(d => d.Minus(5, 1)) .Returns(4); var minusValue = mockInterface.Object.Minus(5, 1); Assert.Equal(4, minusValue); } /// /// 在单个模拟中实现多个接口 /// [Fact] public void Advanced_MulttipleInteface_Single_Test() { var mockDemo = new Mock(); mockDemo.Setup(d => d.Add(1, 3)) .Returns(4); var mockInterface = mockDemo.As() .Setup(d => d.Minus(5, 1)) .Returns(4); //现在mockDemo也实现了IDemo接口,同时有两个方法:原Add()和 IDemo接口方法 Minus() var aadValue = mockDemo.Object.Add(1, 3); var minusValue = mockDemo.Object.Minus(5, 1); Assert.Equal(4, aadValue); Assert.Equal(4, minusValue); } /// /// 自定义匹配方法 /// [Fact] public void Advanced_CustomMatcher_Test() { var mockFoo = new Mock(); mockFoo.Setup(foo => foo.DoSomething(IsLarge())) .Throws(); Assert.False(mockFoo.Object.DoSomething("xyz")); Assert.Throws(() => { mockFoo.Object.DoSomething("a1234567890"); }); } #endregion #region LINQ to Mocks(暂时略过) [Fact(Skip = "暂时略过")] public void Linq_Test() { Assert.True(false, "暂时略过,以后学习"); } #endregion #region xUnit 清理 public void Dispose() { } #endregion } }