From a1cf8f3a22dcfb15a15e0a802d26e0230cdc1c1a Mon Sep 17 00:00:00 2001 From: bicijinlian Date: Sun, 29 Jul 2018 12:56:03 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E6=96=B9=E6=B3=95=20?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=8F=82=E6=95=B0=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MoqStudy.MockModel/DemoModel/Bar.cs | 14 + .../{Class1.cs => DemoModel/Baz.cs} | 3 +- MoqStudy.MockModel/DemoModel/IFoo.cs | 22 + MoqStudy.MockModel/MoqStudy.MockModel.csproj | 6 +- MoqStudy.MockModel/StudyModel/Teacher.cs | 40 + MoqStudy.Test/Class1.cs | 12 - MoqStudy.Test/MoqDemoTest.cs | 1085 +++++++++++++++++ MoqStudy.Test/MoqStudy.Test.csproj | 3 +- MoqStudy.Test/MoqStudyTest.cs | 98 ++ 9 files changed, 1268 insertions(+), 15 deletions(-) create mode 100644 MoqStudy.MockModel/DemoModel/Bar.cs rename MoqStudy.MockModel/{Class1.cs => DemoModel/Baz.cs} (70%) create mode 100644 MoqStudy.MockModel/DemoModel/IFoo.cs create mode 100644 MoqStudy.MockModel/StudyModel/Teacher.cs delete mode 100644 MoqStudy.Test/Class1.cs create mode 100644 MoqStudy.Test/MoqDemoTest.cs create mode 100644 MoqStudy.Test/MoqStudyTest.cs diff --git a/MoqStudy.MockModel/DemoModel/Bar.cs b/MoqStudy.MockModel/DemoModel/Bar.cs new file mode 100644 index 0000000..51ca1a6 --- /dev/null +++ b/MoqStudy.MockModel/DemoModel/Bar.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoqStudy.MockModel +{ + public class Bar + { + public virtual Baz Baz { get; set; } + public virtual bool Submit() { return false; } + } +} diff --git a/MoqStudy.MockModel/Class1.cs b/MoqStudy.MockModel/DemoModel/Baz.cs similarity index 70% rename from MoqStudy.MockModel/Class1.cs rename to MoqStudy.MockModel/DemoModel/Baz.cs index 4f68072..51054f7 100644 --- a/MoqStudy.MockModel/Class1.cs +++ b/MoqStudy.MockModel/DemoModel/Baz.cs @@ -6,7 +6,8 @@ using System.Threading.Tasks; namespace MoqStudy.MockModel { - public class Class1 + public class Baz { + public virtual string Name { get; set; } } } diff --git a/MoqStudy.MockModel/DemoModel/IFoo.cs b/MoqStudy.MockModel/DemoModel/IFoo.cs new file mode 100644 index 0000000..aeba374 --- /dev/null +++ b/MoqStudy.MockModel/DemoModel/IFoo.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoqStudy.MockModel +{ + public interface IFoo + { + Bar Bar { get; set; } + string Name { get; set; } + int Value { get; set; } + bool DoSomething(string value); + bool DoSomething(int number, string value); + string DoSomethingStringy(string value); + bool TryParse(string value, out string outputValue); + bool Submit(ref Bar bar); + int GetCount(); + bool Add(int value); + } +} diff --git a/MoqStudy.MockModel/MoqStudy.MockModel.csproj b/MoqStudy.MockModel/MoqStudy.MockModel.csproj index 70bcc0d..2d0e6a9 100644 --- a/MoqStudy.MockModel/MoqStudy.MockModel.csproj +++ b/MoqStudy.MockModel/MoqStudy.MockModel.csproj @@ -43,11 +43,15 @@ - + + + + + \ No newline at end of file diff --git a/MoqStudy.MockModel/StudyModel/Teacher.cs b/MoqStudy.MockModel/StudyModel/Teacher.cs new file mode 100644 index 0000000..3f20a57 --- /dev/null +++ b/MoqStudy.MockModel/StudyModel/Teacher.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoqStudy.MockModel +{ + public class Teacher + { + public Teacher(){ } + public Teacher(int groupId) + { + GroupId = groupId; + } + + public int Id { get; set; } + public string Name { get; set; } + public int GroupId { get; set; } + + public virtual int Add() + { + return Id + GroupId; + } + + protected virtual int GetAge(int age) + { + return age + 2; + } + + protected virtual bool Execute(int number) + { + if (number == 0) + { + return false; + } + return number % 2 == 0; + } + } +} diff --git a/MoqStudy.Test/Class1.cs b/MoqStudy.Test/Class1.cs deleted file mode 100644 index 13c19a4..0000000 --- a/MoqStudy.Test/Class1.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MoqStudy.Test -{ - public class Class1 - { - } -} diff --git a/MoqStudy.Test/MoqDemoTest.cs b/MoqStudy.Test/MoqDemoTest.cs new file mode 100644 index 0000000..f425bbb --- /dev/null +++ b/MoqStudy.Test/MoqDemoTest.cs @@ -0,0 +1,1085 @@ +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; + 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()); + } + + + #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 Properties_Setup_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + + //设置属性值 + mock.Setup(f => f.Name).Returns("wanggaofeng"); + Assert.Equal("wanggaofeng", ifoo.Name); + + // auto-mocking hierarchies (a.k.a. recursive mocks) + // 自动模拟多层(即递归Mocks) + mock.Setup(f => f.Bar.Baz.Name).Returns("Bar.Baz.Name"); + Assert.Equal("Bar.Baz.Name", ifoo.Bar.Baz.Name); + } + + /// + /// 设置属性:todo SetupSet用法 + /// + [Fact] + public void Properties_SetupSet_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + + //expects an invocation to set the value to "foo" + //期望将调用值设置为“foo” + var cc= mock.SetupSet(foo => foo.Name = "foo"); + Assert.Equal("foo", ifoo.Name); + } + + /// + /// 验证设置属性 + /// + [Fact] + public void Properties_SetupGet_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + + // verify the setter directly + mock.SetupGet(foo => foo.Name); + ifoo.Name = "foo"; + Assert.Equal("foo", ifoo.Name); + } + + /// + /// Setup a property so that it will automatically start tracking its value (also known as Stub) + /// 设置属性以使其自动开始跟踪其值(也称为存根) + /// + [Fact] + public void Properties_SetupProperty_Test() + { + var mock = new Mock(); + + // start "tracking" sets/gets to this property + // 开始“跟踪”设置/获取此属性 + mock.SetupProperty(f => f.Name); + + // alternatively, provide a default value for the stubbed property + // 或者,为插入属性提供默认值。 + mock.SetupProperty(f => f.Name, "foo"); + + + // Now you can do: + // 现在你可以这么做: + + IFoo foo = mock.Object; + // Initial value was stored + // 初始值被存储 + Assert.Equal("foo", foo.Name); + + // New value set which changes the initial value + // 新的设定值改变初始值 + foo.Name = "bar"; + Assert.Equal("bar", foo.Name); + } + + /// + /// Stub all properties on a mock (not available on Silverlight) + /// 存根所有模拟属性 + /// + [Fact] + public void Properties_SetupAllProperties_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + mock.SetupAllProperties(); + + mock.SetupProperty(f => f.Name, "foo"); + Assert.Equal("foo", ifoo.Name); + + ifoo.Name = "change"; + Assert.Equal("change", ifoo.Name); + } + #endregion + + #region 设置事件 + + [Fact] + public void Events_Test() + { + //要单独写测试 + } + #endregion + + #region 回调函数 + + /// + /// 设置回调函数 + /// + [Fact] + public void Callback_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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); + } + + /// + /// access invocation arguments + /// 回调函数中使用参数 + /// + [Fact] + public void Callback_Argument_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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); + + } + + /// + /// alternate equivalent generic method syntax + /// 泛型回调函数中使用参数 + /// + [Fact] + public void Callback_Generic_Argument_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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); + + } + + /// + /// access arguments for methods with multiple parameters + /// 回调函数中使用多个参数 + /// + [Fact] + public void Callback_MultiArgument_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + + 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); + } + + /// + /// callbacks can be specified before and after invocation + /// 指定回调函数位置:前置执行或后置执行 + /// + [Fact] + public void Callback_BeforeAndAfter_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + + 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); + } + + /// + /// callbacks for methods with `ref` / `out` parameters are possible but require some work (and Moq 4.8 or later) + /// 回调函数中使用 re/out 参数 + /// + [Fact] + public void Callback_RefAndOut_Parameter_Test() + { + //var mock = new Mock(); + //mock.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() + { + var mock = new Mock(); + Assert.Throws(() => mock.Verify(f => f.Add(1))); + } + + /// + /// 设置后,不运行方法进行验证 + /// + [Fact] + public void Verify_NoRun_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + mock.Setup(f => f.Add(2)).Returns(true); + + Assert.Throws(() => { mock.Verify(f => f.Add(2)); }); + } + + [Fact] + public void Verify_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + + mock.Setup(f => f.Add(2)).Returns(true); + ifoo.Add(2); + mock.Verify(f => f.Add(2)); + } + + /// + /// Verify with custom error message for failure + /// 在验证失败的时候,提供自定义的错误提示信息 + /// + [Fact] + public void Verify_ErrorMessage_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + Assert.Throws(() => { mock.Verify(foo => foo.DoSomething("ping"), "When doing operation X, the service should be pinged always"); }); + } + + /// + /// Method should never be called + /// 方法从未被调用 + /// + [Fact] + public void Verify_Never_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + + mock.Verify(foo => foo.DoSomething("ping"), Times.Never()); + } + + /// + /// Called at least once + /// 至少调用过一次 + /// + [Fact] + public void Verify_AtLeastOnce_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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); + + } + + /// + /// Verify getter invocation, regardless of value + /// 验证属性被读取过 + /// + [Fact] + public void Verify_Propertie_Get_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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 rr = ifoo.Name; + mock.VerifyGet(foo => foo.Name, customInfo); + mockException = null; + } + catch (MockException ex) + { + mockException = ex; + } + + Assert.Null(mockException); + + } + + /// + /// Verify setter invocation, regardless of value. + /// 验证对属性的赋值(已弃用的方法) + /// + [Fact] + public void Verify_Propertie_Set_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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); + + } + + /// + /// Verify setter invocation, regardless of value + /// 验证对于属性设置特定的值 + /// + [Fact] + public void Verify_PropertieValue_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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); + } + + /// + /// Verify setter with an argument matcher + /// 验证匹配的参数 + /// + [Fact] + public void Verify_Propertie_Argument_Test() + { + var mock = new Mock(); + var ifoo = mock.Object; + 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 定制模拟行为 + + /// + /// Customizing Mock Behavior + /// 定制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 + /// 使用Callbase + /// 如果没有重写基类的实现,默认将不会调用基类,在 Mock Web/Html 控件的是必须的 + /// + [Fact] + public void CustomMock_Callbase_Test() + { + /* + Invoke base class implementation if no expectation overrides the member + (a.k.a. "Partial Mocks" in Rhino Mocks): default is false. + (this is required if you are mocking Web/Html controls in System.Web!) + */ + //如果没有重写基类的实现,默认将不会调用基类,在 Mock Web/Html 控件的是必须的。 + var mock = new Mock { CallBase = true }; + + //todo:需要验证 + } + + /// + /// 创造自动递归的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 }; + + // Create a mock using the repository settings + var fooMock = repository.Create(); + + // Create a mock overriding the repository settings + 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() + { + //todo:用法还不清楚 + var mock = new Mock(); + mock.Protected() + .Setup("GetAge",2) + .Returns(5); + //todo:设置好了,怎么使用是个问题 + + // if you need argument matching, + // you MUST use ItExpr rather than It planning on improving this for vNext + // (see below for an alternative in Moq 4.8) + mock.Protected() + .Setup("Execute", ItExpr.IsAny()) + .Returns(true); + } + #endregion + + #region 高级特性 + [Fact] + public void Advanced_GetFromMocked_Test() + { + // get mock from a mocked instance + IFoo foo =new Mock().Object; // get mock instance somehow 以某种方式获取模拟实例 + var fooMock = Mock.Get(foo); + fooMock.Setup(f => f.GetCount()).Returns(42); + + Assert.Equal(42, fooMock.Object.GetCount()); + } + + + /// + /// implementing multiple interfaces in mock + /// 在模拟中实现多个接口 + /// + [Fact] + public void Advanced_MulttipleInteface_Test() + { + var mock = new Mock(); + + var disposableFoo = mock.As(); + // now the IFoo mock also implements IDisposable + // 现在,IFoo 模拟也实现了IDisposable接口 + disposableFoo.Setup(disposable => disposable.Dispose()); + + var ifoo = disposableFoo.Object; + + ifoo.Dispose(); + //todo:设计测试方法 + } + + /// + /// implementing multiple interfaces in single mock + /// 在单个模拟中实现多个接口 + /// + [Fact] + public void Advanced_MulttipleInteface_Single_Test() + { + var mock = new Mock(); + mock.Setup(foo => foo.Name).Returns("Fred"); + mock.As().Setup(disposable => disposable.Dispose()); + } + + /// + /// 自定义匹配方法 + /// + [Fact] + public void Advanced_CustomMatcher_Test() + { + //var mock = new Mock(); + //var ifoo = mock.Object; + //mock.Setup(foo => foo.DoSomething(IsLarge())) + // .Throws(); + + //Assert.False(ifoo.DoSomething("xyz")); + + //Assert.Throws(()=> { ifoo.DoSomething("a1234567890"); }); + + } + #endregion + + #region LINQ to Mocks + #endregion + + #region xUnit 清理 + public void Dispose() + { + + } + #endregion + } +} diff --git a/MoqStudy.Test/MoqStudy.Test.csproj b/MoqStudy.Test/MoqStudy.Test.csproj index 72411d7..e9e2851 100644 --- a/MoqStudy.Test/MoqStudy.Test.csproj +++ b/MoqStudy.Test/MoqStudy.Test.csproj @@ -69,7 +69,8 @@ - + + diff --git a/MoqStudy.Test/MoqStudyTest.cs b/MoqStudy.Test/MoqStudyTest.cs new file mode 100644 index 0000000..bd20885 --- /dev/null +++ b/MoqStudy.Test/MoqStudyTest.cs @@ -0,0 +1,98 @@ +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 Xunit.Abstractions; +using Xunit.Extensions; +using Xunit.Sdk; + +using MoqStudy.MockModel; + +namespace MoqStudy.Test +{ + /// + /// Moq 模拟框架学习 + /// + public class MoqStudyTest + { + #region 创建Mock对象 + /// + /// 使用默认设置创建默认对象 + /// + [Fact] + public void Create_Default_Mock_Test() + { + var mock = new Mock(); + mock + .Setup(t => t.Add()) + .Returns(88); + var teacher = mock.Object; + + Assert.NotNull(teacher); + Assert.Equal(0, teacher.GroupId); + Assert.Equal(88, teacher.Add()); + } + + /// + /// 给对象构造函数传递参数,创建对象 + /// + [Fact] + public void Create_Params_Mock_Test() + { + var groupId = 8;//Teacher类,构造函数的参数 + var addResult = 88; + var mock = new Mock(groupId); + mock.Setup(t => t.Add()).Returns(addResult); + var teacher = mock.Object; + + Assert.NotNull(teacher); + Assert.Equal(groupId, teacher.GroupId); + Assert.Equal(addResult, teacher.Add()); + } + + /// + /// 使用MockBehavior选项设置,创建默认对象 + /// MockBehavior.Default 默认值即是 MockBehavior.Loose + /// MockBehavior.Loose 宽松模式:未显式设置的,取默认值 + /// MockBehavior.Strict 严格模式:未显式设置的,报异常 + /// + [Fact] + public void Create_MockBehavior_Mock_Test() + { + var mock = new Mock(MockBehavior.Loose); + mock.Setup(t => t.Add()).Returns(88); + var teacher = mock.Object; + + Assert.NotNull(teacher); + Assert.Equal(0, teacher.GroupId); + Assert.Equal(88, teacher.Add()); + } + + /// + /// 使用MockBehavior选项设置,并传递参数给构造函数,创建对象 + /// + [Fact] + public void Create_MockBehaviorAndParams_Mock_Test() + { + var groupId = 9; + var addResult = 88; + + var mock = new Mock(MockBehavior.Loose, groupId); + mock.Setup(t => t.Add()).Returns(addResult); + var teacher = mock.Object; + + Assert.NotNull(teacher); + Assert.Equal(groupId, teacher.GroupId); + Assert.Equal(addResult, teacher.Add()); + } + #endregion + } +}