Moq - 基础回顾
- Method 方法#
- Properties 属性#
- Event 事件#
- Callbacks 回调#
- 1. 基本用法:统计方法调用次数#
- 2. 捕获方法参数#
- 3. 使用泛型方法捕获参数#
- 4. 捕获多参数方法#
- 5. 在返回前后设置回调#
- 6. 处理 ref / out 参数#
- 7. 动态返回递增值#
- 8. 将参数值赋给模拟对象的属性#
- 总结#
- Verification 验证#
- 1. 验证方法调用次数#
- 2. 带自定义错误信息的验证#
- 3. 验证方法未被调用#
- 4. 验证方法至少被调用一次#
- 5. 验证属性访问#
- 6. 验证属性设置#
- 7. 验证属性设置的具体值#
- 8. 验证属性设置的条件#
- 9. 验证事件监听器的添加和移除#
- 10. 验证没有其他未验证的调用#
- 总结#
- Customizing Mock Behavior 自定义模拟行为#
- Miscellaneous 杂项#
- Advanced Features 高级功能#
- 1. 从实例中获取模拟对象#
- 2. 实现多重接口#
- 3. 实现多个接口#
- 4. 自定义匹配器#
- 5. 模拟内部类型#
- 6. 自定义默认值生成策略#
- 7. 匹配泛型类型参数#
- 8. 使用类型匹配器(Moq 4.13+)#
- 总结#
- LINQ to Mocks#
- 1. 核心概念:通过 LINQ 表达式定义模拟行为#
- 2. 在单一模拟对象上设置多个行为#
- 3. 设置多个级联依赖#
- 4. 优势与用途#
- 5. 注意事项#
- 总结#
Method 方法#
有以下例子
using Moq;
// Assumptions:
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);
Task<bool> DoSomethingAsync();
string DoSomethingStringy(string value);
bool TryParse(string value, out string outputValue);
bool Submit(ref Bar bar);
int GetCount();
bool Add(int value);
}
public class Bar
{
public virtual Baz Baz { get; set; }
public virtual bool Submit() { return false; }
}
public class Baz
{
public virtual string Name { get; set; }
}
var mock = new Mock<IFoo>();
mock.Setup(foo => foo.DoSomething("ping")).Returns(true);
这行代码设置了当 mock 对象的 DoSomething 方法被调用,并且传入的参数是 "ping" 时,返回 true。
具体来说:
1 Setup
方法用于定义模拟对象的行为。
foo => foo.DoSomething("ping") 是一个 lambda 表达式,表示当 DoSomething 方法被调用且参数为 "ping" 时。
2 Returns(true)
指定了 DoSomething 方法在上述条件下返回 true。
mock.Setup(x => x.DoSomethingStringy(It.IsAny<string>()))
.Returns((string s) => s.ToLower());
1 It.IsAny<string>()
表示匹配任何字符串参数
2 (string s) => s.ToLower()
是一个 lambda 表达式,表示 DoSomethingStringy 方法的返回值是传入字符串参数的全小写形式。
Async Methods 异步方法#
- 从Moq 4.16开始,您可以简单地mock.Setup返回任务的.Result属性。这适用于几乎所有的设置和验证表达式:
mock.Setup(foo => foo.DoSomethingAsync().Result).Returns(true);
- 在早期版本中,在可用的地方使用安装帮助方法,例如
setup.ReturnsAsync
、setup.ThrowsAsync
:
mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true);
匹配参数#
// any value 任意值
mock.Setup(foo => foo.DoSomething(It.IsAny<string>())).Returns(true);
// any value passed in a `ref` parameter
(requires Moq 4.8 or later):
mock.Setup(foo => foo.Submit(ref It.Ref<Bar>.IsAny)).Returns(true);
// matching Func<int>, lazy evaluated
mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 ==
0))).Returns(true);
// matching ranges
mock.Setup(foo => foo.Add(It.IsInRange<int>(0, 10,
Range.Inclusive))).Returns(true);
// matching regex
mock.Setup(x => x.DoSomethingStringy(It.IsRegex("[a-d]+",
RegexOptions.IgnoreCase))).Returns("foo");
Properties 属性#
-
Setup
是用于方法的设置,而 SetupSet 和 SetupProperty 是用于属性的设置。 -
SetupSet
主要用于验证属性的设置操作,但已过时,建议使用 SetupProperty。 -
SetupProperty
不仅可以设置属性的初始值,还会自动实现 getter 和 setter,便于在测试中使用。 -
SetupAllProperties()
的作用是为模拟对象的所有属性自动设置默认的 getter 和 setter 行为。这意味着你可以在测试中直接访问和修改这些属性的值,而无需显式地为每个属性调用 Setup 或 SetupProperty。
1 设置属性返回值:
mock.Setup(foo => foo.Name).Returns("bar");
这行代码设置了 foo 对象的 Name 属性,当访问该属性时,将返回 "bar"。
2 自动模拟层次结构(递归模拟):
mock.Setup(foo => foo.Bar.Baz.Name).Returns("baz");
这行代码设置了 foo 对象的 Bar 属性的 Baz 属性的 Name 属性,当访问该属性时,将返回 "baz"。这展示了 Moq 的递归模拟功能,可以自动创建嵌套的模拟对象。
3 设置属性值的期望:
mock.SetupSet(foo => foo.Name = "foo");
这行代码设置了一个期望,即 foo 对象的 Name 属性将被设置为 "foo"。
4 验证属性设置:
mock.VerifySet(foo => foo.Name = "foo");
这行代码验证了 foo 对象的 Name 属性是否被设置为 "foo"。如果没有被设置为 "foo",则测试将失败。
5 设置属性:
mock.SetupProperty(f => f.Name);
这行代码使用 SetupProperty 方法来设置 Name 属性,使其可以被读取和写入。默认情况下,属性的初始值为类型的默认值(对于字符串来说是 null)。
设置属性并提供默认值:
mock.SetupProperty(f => f.Name, "foo");
这行代码不仅设置了 Name 属性,还为其提供了一个默认值 "foo"。这意味着在测试开始时,Name 属性的值将是 "foo"。
使用模拟对象:
IFoo foo = mock.Object;
这行代码获取了模拟对象 mock 的实际对象实例 foo。
断言初始值:
Assert.Equal("foo", foo.Name);
这行代码断言 foo.Name 的初始值是 "foo",验证 SetupProperty 方法设置的默认值是否正确。
设置新值并断言:
foo.Name = "bar";
Assert.Equal("bar", foo.Name);
这两行代码首先将 foo.Name 属性设置为 "bar",然后断言 foo.Name 的值确实被更新为 "bar"。
mock.SetupAllProperties()
#
- 作用
SetupAllProperties()
的作用是为模拟对象的所有属性自动设置默认的 getter 和 setter 行为。这意味着你可以在测试中直接访问和修改这些属性的值,而无需显式地为每个属性调用 Setup
或 SetupProperty
。
- 示例
假设你有一个接口 IPerson
,其中定义了一个属性 Name
:
public interface IPerson
{
string Name { get; set; }
}
你可以使用 SetupAllProperties()
来设置该属性的行为:
var mock = new Mock<IPerson>();
mock.SetupAllProperties();
// 现在你可以直接设置和获取属性
mock.Object.Name = "John";
Console.WriteLine(mock.Object.Name); // 输出: John
- 特点
-
自动设置:无需手动为每个属性调用
SetupProperty
,可以一次性为所有属性设置默认行为。 -
简化配置:当需要为多个属性设置行为时,
SetupAllProperties()
可以节省代码量,避免重复调用SetupProperty
。 -
支持对属性的读写操作:设置后,你可以像操作普通属性一样对模拟对象的属性进行读写操作。
- 注意事项
-
与
SetupProperty
的区别:SetupProperty
需要逐个为属性设置行为,而SetupAllProperties()
是自动设置所有属性的行为。 -
属性值的记录:虽然
SetupAllProperties()
允许你操作属性,但它不会像SetupProperty
那样自动记录属性的值变化。如果需要记录属性值,可以配合Callback
或其他方法来实现。
总结
SetupAllProperties()
是一个便捷的方法,用于自动为模拟对象的所有属性设置默认的 getter 和 setter 行为。它可以简化代码,减少重复调用,适合需要快速设置多个属性测试场景。
Event 事件#
这段代码是关于使用 Moq 框架来模拟和测试事件行为的示例。以下是对代码中各个部分的解释:
1. SetupAdd
和 SetupRemove
#
mock.SetupAdd(m => m.FooEvent += It.IsAny<EventHandler>())...;
mock.SetupRemove(m => m.FooEvent -= It.IsAny<EventHandler>())...;
-
SetupAdd
:用于模拟事件的订阅(+=
)操作。m => m.FooEvent += It.IsAny<EventHandler>()
表示无论传递什么样的EventHandler
,都将触发设置为默认行为。 -
SetupRemove
:用于模拟事件的退订(-=
)操作。m => m.FooEvent -= It.IsAny<EventHandler>()
表示无论移除哪个EventHandler
,都将触发设置为默认行为。 -
用途:通过
SetupAdd
和SetupRemove
,可以精确控制事件订阅和退订的行为,例如触发某些行为或验证事件是否正确地被订阅或退订。
2. Raise
方法#
mock.Raise(m => m.FooEvent += null, new FooEventArgs(fooValue));
mock.Raise(m => m.FooEvent += null, this, new FooEventArgs(fooValue));
mock.Raise(m => m.Child.First.FooEvent += null, new FooEventArgs(fooValue));
-
Raise
:用于触发(模拟)事件的调用。 -
m => m.FooEvent += null
:表示事件的订阅操作(+=
),但null
是占位符,用于表示事件的触发。 -
参数:
-
new FooEventArgs(fooValue)
:传递给事件的参数。 -
this
:可选的发送者对象。
-
-
用途:通过
Raise
方法,可以在测试中模拟事件的触发,从而验证代码对事件响应的正确性。
3. Raises
方法结合 Setup
#
mock.Setup(foo => foo.Submit()).Raises(f => f.Sent += null, EventArgs.Empty);
-
Setup
:设置对Submit
方法的模拟。 -
Raises
:当调用Submit
方法时,自动触发Sent
事件。 -
EventArgs.Empty
:传递给事件的参数。 -
用途:将方法调用与事件触发绑定在一起,当调用某个方法时,自动触发事件,从而触发代码中的事件处理逻辑。
4. Raise
方法与自定义事件#
public delegate void MyEventHandler(int i, bool b);
public interface IFoo
{
event MyEventHandler MyEvent;
}
var mock = new Mock<IFoo>();
mock.Raise(foo => foo.MyEvent += null, 25, true);
-
自定义事件:
MyEventHandler
是一个自定义的事件委托,它接受两个参数(int
和bool
)。 -
Raise
方法:触发MyEvent
事件,并传递自定义参数25
和true
。 -
用途:即使事件不遵循标准的
EventHandler
模式,Raise
方法仍然可以模拟其行为。
5. 总结#
-
SetupAdd
和SetupRemove
:用于精确控制事件的订阅和退订行为。 -
Raise
方法:用于模拟事件的触发,验证代码对事件的响应。 -
Raises
方法:将方法调用与事件触发绑定,简化事件测试的设置。 -
自定义事件:
Raise
方法支持非标准的事件委托,适用于各种事件场景。
通过这些方法,可以有效地模拟和测试事件的行为,确保代码在事件触发时能够正确地工作。
Callbacks 回调#
这段代码展示了如何使用 Moq 框架来配置和测试模拟对象的行为,特别是通过回调(Callbacks)和参数捕获(Argument Capture)来增强测试功能。以下是代码的详细解释:
1. 基本用法:统计方法调用次数#
mock.Setup(foo => foo.DoSomething("ping"))
.Callback(() => calls++)
.Returns(true);
-
Setup
:设置对DoSomething
方法的模拟,当该方法被调用时,满足条件才会触发模拟行为。 -
Callback
:定义一个回调操作,calls++
用于统计该方法的调用次数。 -
Returns
:指定方法的返回值。
2. 捕获方法参数#
mock.Setup(foo => foo.DoSomething(It.IsAny<string>()))
.Callback((string s) => callArgs.Add(s))
.Returns(true);
-
It.IsAny<string>()
:表示任何字符串参数都会触发此模拟。 -
Callback
:捕获方法的参数值,并存储到callArgs
列表中。
3. 使用泛型方法捕获参数#
mock.Setup(foo => foo.DoSomething(It.IsAny<string>()))
.Callback<string>(s => callArgs.Add(s))
.Returns(true);
Callback<string>
:明确指定参数类型为字符串,使代码更清晰。
4. 捕获多参数方法#
mock.Setup(foo => foo.DoSomething(It.IsAny<int>(), It.IsAny<string>()))
.Callback<int, string>((i, s) => callArgs.Add(s))
.Returns(true);
-
It.IsAny<int>()
:匹配任何整数参数。 -
Callback<int, string>
:捕获两个参数,只存储字符串参数的值。
5. 在返回前后设置回调#
mock.Setup(foo => foo.DoSomething("ping"))
.Callback(() => Console.WriteLine("Before returns"))
.Returns(true)
.Callback(() => Console.WriteLine("After returns"));
Callback
:设置两个回调,分别在返回前和返回后触发。
6. 处理 ref
/ out
参数#
mock.Setup(foo => foo.Submit(ref It.Ref<Bar>.IsAny))
.Callback(new SubmitCallback((ref Bar bar) => Console.WriteLine("Submitting a Bar!")));
-
It.Ref<Bar>.IsAny
:表示任何Bar
类型的ref
参数。 -
Callback
:通过自定义委托 (SubmitCallback
) 捕获ref
参数。
7. 动态返回递增值#
mock.Setup(foo => foo.GetCount())
.Callback(() => calls++)
.Returns(() => calls);
-
Callback
:每次调用时递增calls
。 -
Returns
:动态返回当前的calls
值。
8. 将参数值赋给模拟对象的属性#
mock.SetupProperty(foo => foo.Bar);
mock.Setup(foo => foo.DoSomething(It.IsAny<string>()))
.Callback((string s) => mock.Object.Bar = s)
.Returns(true);
-
SetupProperty
:自动实现Bar
属性的 getter 和 setter。 -
Callback
:将捕获到的参数值赋给mock.Object.Bar
。
总结#
-
callback
:用于在方法调用时插入自定义逻辑。 -
It.IsAny
:匹配任意参数值。 -
Parameters Capture
:捕获方法调用参数,方便后续断言或检查。 -
Ref
和out
参数:需要使用It.Ref<Bar>.IsAny
并配合委托来处理。 -
动态返回值:可通过闭包捕获变量,实现动态返回逻辑。
-
自动属性:
SetupProperty
用于模拟属性的行为,方便状态测试。
通过这些功能,Moq 提供了强大的灵活性,可用于创建复杂的测试场景。
Verification 验证#
这组代码展示了如何使用 Moq 的 Verify
方法来验证模拟对象(Mock Object)的行为是否符合预期。以下是代码的详细解释:
1. 验证方法调用次数#
mock.Verify(foo => foo.DoSomething("ping"));
-
Verify
:用于验证模拟对象的某个方法是否被调用。 -
foo => foo.DoSomething("ping")
:表示验证DoSomething
方法是否被调用,且参数为"ping"
。 -
默认行为:如果没有指定调用次数的要求,默认验证方法是否至少被调用一次。
2. 带自定义错误信息的验证#
mock.Verify(foo => foo.DoSomething("ping"), "When doing operation X, the service should be pinged always");
"When doing operation X, the service should be pinged always"
:当验证失败时,输出自定义的错误信息。
3. 验证方法未被调用#
mock.Verify(foo => foo.DoSomething("ping"), Times.Never());
Times.Never()
:指定方法应从未被调用。
4. 验证方法至少被调用一次#
mock.Verify(foo => foo.DoSomething("ping"), Times.AtLeastOnce());
Times.AtLeastOnce()
:指定方法至少被调用了一次。
5. 验证属性访问#
mock.VerifyGet(foo => foo.Name);
VerifyGet
:验证某个属性的getter
是否被调用。
6. 验证属性设置#
mock.VerifySet(foo => foo.Name);
VerifySet
:验证某个属性的setter
是否被调用。
7. 验证属性设置的具体值#
mock.VerifySet(foo => foo.Name = "foo");
foo => foo.Name = "foo"
:验证setter
是否被调用,且被设置的值为"foo"
。
8. 验证属性设置的条件#
mock.VerifySet(foo => foo.Value = It.IsInRange(1, 5, Range.Inclusive));
It.IsInRange
:指定验证属性被设置时的值是否在 1 到 5(含)的范围内。
9. 验证事件监听器的添加和移除#
mock.VerifyAdd(foo => foo.FooEvent += It.IsAny<EventHandler>());
mock.VerifyRemove(foo => foo.FooEvent -= It.IsAny<EventHandler>());
-
VerifyAdd
:验证事件的订阅(+=
)操作是否被调用。 -
VerifyRemove
:验证事件的退订(-=
)操作是否被调用。
10. 验证没有其他未验证的调用#
mock.VerifyNoOtherCalls();
- 用途:确保没有其他未被验证的调用发生,即模拟对象的行为完全符合预期。
总结#
Verify
方法是用于验证模拟对象的行为是否符合预期的关键工具,支持多种验证场景,包括方法调用次数、属性访问、事件监听器的添加和移除等。通过这些工具,可以确保代码在测试中的行为与设计相符。
Customizing Mock Behavior 自定义模拟行为#
这段介绍详细阐述了 Moq 框架中如何自定义模拟对象(Mock)的行为。以下是对每个部分的详细解释:
1. 自定义模拟行为#
-
"Strict" Mock vs. "Loose" Mock:
-
Loose Mock(默认行为):如果没有为某个成员设置期望,Loose Mock 不会抛出异常,而是返回默认值(如
null
、0、空数组等)。 -
Strict Mock:如果没有为某个成员设置期望,Strict Mock 会抛出异常。这种行为使得测试更加严格,确保所有调用都是预期中的。
-
var mock = new Mock<IFoo>(MockBehavior.Strict);
- 通过将
MockBehavior
设置为Strict
,创建一个严格的模拟对象。
2. Partial Mocks#
- Partial Mocks:如果设置
CallBase
为true
,当没有为某个成员设置期望时,模拟对象将调用基类的实现。这种方式在模拟 Web 或 HTML 控件时特别有用。
var mock = new Mock<IFoo> { CallBase = true };
- 这行代码创建了一个部分模拟对象,允许调用基类的方法。
3. 自动递归模拟#
- 自动递归模拟:设置
DefaultValue
为DefaultValue.Mock
,使得每当访问一个没有设置期望的可模拟成员时,返回一个新的模拟对象。这种方式适用于需要嵌套模拟的情况。
var mock = new Mock<IFoo> { DefaultValue = DefaultValue.Mock };
- 这行代码创建了一个自动递归的模拟对象。
Bar value = mock.Object.Bar;
- 访问
Bar
属性时,如果没有设置期望,将返回一个新的Bar
模拟对象。
4. 重用模拟实例#
- 重用:返回的模拟对象是重用的,因此后续对该属性的访问将返回相同的模拟实例。这使得可以对该实例设置进一步的期望。
var barMock = Mock.Get(value);
barMock.Setup(b => b.Submit()).Returns(true);
- 通过
Mock.Get(value)
获取到Bar
的模拟实例,并设置其Submit
方法的期望。
5. 集中管理模拟实例#
- MockRepository:使用
MockRepository
可以集中创建和管理所有模拟对象。可以一致地设置MockBehavior
、CallBase
和DefaultValue
。
var repository = new MockRepository(MockBehavior.Strict) { DefaultValue = DefaultValue.Mock };
- 创建一个严格的模拟仓库,默认返回值为新的模拟对象。
var fooMock = repository.Create<IFoo>();
- 使用仓库创建一个
IFoo
的模拟对象。
var barMock = repository.Create<Bar>(MockBehavior.Loose);
- 创建一个
Bar
的模拟对象,但使用松散的行为。
6. 验证所有期望#
repository.Verify();
- 通过
Verify
方法验证所有在仓库中创建的模拟对象上的可验证期望。这确保了在测试中所有的期望都得到了满足。
总结#
这段介绍涵盖了 Moq 框架中模拟对象的多种自定义行为,包括严格和松散的模拟、部分模拟、自动递归模拟、集中管理模拟实例等。通过这些功能,开发者可以灵活地创建和管理模拟对象,以便进行有效的单元测试。
Miscellaneous 杂项#
解释#
这段代码涵盖了 Moq 框架中的一些高级用法,以下是各部分内容的详细解释:
1. 重置模拟对象#
mock.Reset();
-
用途:清除模拟对象的所有设置(包括
Setup
、默认返回值、事件处理器和记录的调用信息),使模拟对象恢复到初始状态。 -
场景:在单元测试中,当需要多次使用同一模拟对象且希望在每次测试之间保持独立性时,可以使用
Reset
方法。
2. 设置成员按顺序返回不同值或抛出异常#
mock.SetupSequence(f => f.GetCount())
.Returns(3)
.Returns(2)
.Returns(1)
.Returns(0)
.Throws(new InvalidOperationException());
-
用途:使用
SetupSequence
为模拟对象的成员设置一系列不同的返回值或异常。每次调用该成员时,会按顺序返回或抛出指定的值或异常。 -
场景:模拟一个随着时间或状态变化而返回不同值的函数,例如模拟一个计数器或网络请求的状态变化。
3. 验证方法调用顺序#
var sequence = new MockSequence();
_fooService.InSequence(sequence).Setup(x => x.FooMethod(a)).ReturnsAsync(b);
_barService.InSequence(sequence).Setup(x => x.BarMethod(c)).ReturnsAsync(d);
_bazService.InSequence(sequence).Setup(x => x.BazMethod(e)).ReturnsAsync(f);
-
用途:通过
MockSequence
确保方法调用的顺序。如果方法调用的顺序与设置的预期不一致,测试将失败。 -
场景:验证业务逻辑中多个方法调用的顺序,例如在工作流或事件处理中。
4. 模拟保护成员#
使用字符串名称访问保护成员
mock.Protected().Setup<int>("Execute").Returns(5);
mock.Protected().Setup<bool>("Execute", ItExpr.IsAny<string>()).Returns(true);
-
用途:模拟引用对象的保护方法或字段。
-
注意:访问保护成员时不会触发 IntelliSense,需要手动指定成员名称。
-
场景:测试需要访问或模拟基类的保护成员时。
使用接口类型访问保护成员
interface CommandBaseProtectedMembers
{
bool Execute(string arg);
}
mock.Protected().As<CommandBaseProtectedMembers>()
.Setup(m => m.Execute(It.IsAny<string>()))
.Returns(true);
-
用途:通过定义一个与实际类保护成员具有相同方法签名的接口,可以在模拟时获得 IntelliSense 支持。
-
场景:当需要模拟保护成员且希望获得代码自动补全支持时。
5. 其他杂项功能#
-
Reset
方法:用于重置模拟对象的所有状态。 -
SetupSequence
:支持为方法设置一系列不同的返回值或异常。 -
InSequence
:用于验证方法调用顺序。 -
Protected
:用于访问和模拟引用对象的保护成员。 -
ItExpr
:在模拟保护成员时用于匹配参数。
总结#
Moq 提供了丰富的功能来模拟对象的行为,包括重置模拟对象、按顺序设置返回值或异常、验证方法调用顺序以及模拟保护成员等。这些功能使得单元测试更加灵活和强大,可以更好地模拟各种复杂的场景。
Advanced Features 高级功能#
这组代码展示了 Moq 框架的一些高级功能,以下是每个部分的详细解释:
1. 从实例中获取模拟对象#
IFoo foo = // get mock instance somehow
var fooMock = Mock.Get(foo);
fooMock.Setup(f => f.GetCount()).Returns(42);
-
Mock.Get
:从一个已经存在的模拟实例中获取模拟对象。 -
用途:当你需要动态地对已经创建的模拟对象进行进一步的配置时,可以通过
Mock.Get
获取该模拟对象的配置接口。
2. 实现多重接口#
var mock = new Mock<IFoo>();
var disposableFoo = mock.As<IDisposable>();
// now the IFoo mock also implements IDisposable :)
disposableFoo.Setup(disposable => disposable.Dispose());
-
As<T>
:使模拟对象实现多个接口。 -
用途:如果你需要测试的对象依赖于多个接口,可以通过
As<T>
为同一个模拟对象实现多个接口。
3. 实现多个接口#
var mock = new Mock<IFoo>();
mock.Setup(foo => foo.Name).Returns("Fred");
mock.As<IDisposable>().Setup(disposable => disposable.Dispose());
-
As<T>
:为同一个模拟对象实现多个接口。 -
用途:在一个模拟对象上模拟多个接口的行为。
4. 自定义匹配器#
将所有大于 100 字符的字符串视为匹配。
mock.Setup(foo => foo.DoSomething(IsLarge())).Throws<ArgumentException>();
...
[Matcher]
public string IsLarge()
{
return Match.Create<string>(s => !String.IsNullOrEmpty(s) && s.Length > 100);
}
-
[Matcher]
:自定义参数匹配逻辑。 -
Match.Create
:创建一个自定义的匹配项。 -
用途:用于更复杂的参数匹配逻辑。
5. 模拟内部类型#
通过添加特性,可以在强命名或非强命名项目中模拟内部类型。
[assembly:InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
- 用途:模拟包含
internal
类型的项目。
6. 自定义默认值生成策略#
class MyEmptyDefaultValueProvider : LookupOrFallbackDefaultValueProvider
{
public MyEmptyDefaultValueProvider()
{
base.Register(typeof(string), (type, mock) => "?");
base.Register(typeof(List<>), (type, mock) => Activator.CreateInstance(type));
}
}
var mock = new Mock<IFoo> { DefaultValueProvider = new MyEmptyDefaultValueProvider() };
var name = mock.Object.Name; // => "?"
-
DefaultValueProvider
:自定义模拟对象的默认值生成策略。 -
LookupOrFallbackDefaultValueProvider
:基类,用于实现自定义的默认值生成逻辑。
7. 匹配泛型类型参数#
在 Moq 4.8 及以上版本中,可以通过自定义泛型匹配策略来设置对泛型类型参数的期望路径。
通过类型匹配器匹配泛型类型参数:
public interface IFoo
{
bool M1<T>();
bool M2<T>(T arg);
}
var mock = new Mock<IFoo>();
Generic arguments are matched using the usual type polymorphism rules, so if you want to match any type, you can simply use object as type argument in many cases:
mock.Setup(m => m.M1<object>()).Returns(true);
8. 使用类型匹配器(Moq 4.13+)#
在某些情况下,使用 It.IsAnyType
作为类型参数的占位符来匹配任何类型:
// matches any type argument:
mock.Setup(m => m.M1<It.IsAnyType>()).Returns(true);
// matches only type arguments that are subtypes of / implement T:
mock.Setup(m => m.M1<It.IsSubtype<T>>()).Returns(true);
// use of type matchers is allowed in the argument list:
mock.Setup(m => m.M2(It.IsAny<It.IsAnyType>())).Returns(true);
mock.Setup(m => m.M2(It.IsAny<It.IsSubtype<T>>())).Returns(true);
总结#
Moq 框架提供了非常强大的功能,用于模拟对象和接口,支持复杂的测试场景。借助这些高级功能,你可以灵活地模拟各种行为,以确保代码在不同情况下的正确性。
LINQ to Mocks#
Moq 是一个强大的 .NET 模拟框架,它允许通过 LINQ 表达式来声明式地定义模拟对象的行为,这种方式被称为 LINQ to Mocks。LINQ to Mocks 的核心思想是将模拟对象的定义和行为描述为 LINQ 查询,从而以更加简洁、直观的方式创建模拟对象。
1. 核心概念:通过 LINQ 表达式定义模拟行为#
var services = Mock.Of<IServiceProvider>(sp =>
sp.GetService(typeof(IRepository)) == Mock.Of<IRepository>(r => r.IsAuthenticated == true) &&
sp.GetService(typeof(IAuthentication)) == Mock.Of<IAuthentication>(a => a.AuthenticationType == "OAuth"));
-
Mock.Of<T>
:通过 LINQ 表达式创建一个模拟对象。 -
表达式逻辑:上述代码定义了一个
IServiceProvider
的模拟对象,它为不同类型的服务提供了不同的实现:-
IRepository
:返回一个模拟对象,其IsAuthenticated
属性为true
。 -
IAuthentication
:返回一个模拟对象,其AuthenticationType
属性为"OAuth"
。
-
2. 在单一模拟对象上设置多个行为#
ControllerContext context = Mock.Of<ControllerContext>(ctx =>
ctx.HttpContext.User.Identity.Name == "kzu" &&
ctx.HttpContext.Request.IsAuthenticated == true &&
ctx.HttpContext.Request.Url == new Uri("http://moq.github.io/moq4/") &&
ctx.HttpContext.Response.ContentType == "application/xml");
-
目的:通过 LINQ 表达式为
ControllerContext
的模拟对象设置多个行为。 -
多个条件:代码中定义了多个条件,如用户的名称、认证状态、请求的 URL 和响应的内容类型。
3. 设置多个级联依赖#
var context = Mock.Of<ControllerContext>(ctx =>
ctx.HttpContext.Request.Url == new Uri("http://moqthis.me") &&
ctx.HttpContext.Response.ContentType == "application/xml" &&
ctx.HttpContext.GetSection("server") == Mock.Of<ServerSection>(config =>
config.Server.ServerUrl == new Uri("http://moqthis.com/api")));
-
级联模拟对象:
Mock.Of
可以创建包含其他模拟对象的模拟对象,从而模拟复杂的依赖关系。 -
ctx.HttpContext.GetSection("server")
:返回一个ServerSection
的模拟对象,其ServerUrl
属性被设置为指定的值。
4. 优势与用途#
-
简洁性:LINQ to Mocks 通过声明式语法减少了样板代码,使代码更简洁。
-
易读性:通过 LINQ 表达式,模拟对象的行为和期望可以更直观地表示。
-
快速设置:适用于不需要验证的依赖项的快速存根。
5. 注意事项#
-
Mock.Get
方法:如果需要在测试中验证 LINQ to Mocks 创建的模拟对象的行为,可以通过Mock.Get
方法获取模拟对象的配置实例。 -
错误处理:代码片段中的
<url>
替换为实际的 URL,否则可能会导致编译错误。
总结#
LINQ to Mocks 是 Moq 的一项强大功能,它通过 LINQ 表达式提供了一种简洁、直观的方式来定义模拟对象的行为。虽然它的语法可能需要一些时间来熟悉,但在处理复杂的模拟场景时,它可以显著提高代码的可读性和可维护性。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
2022-02-11 (6.1)分栏布局
2015-02-11 EasyUi–8.datebox赋值的问题
2015-02-11 MVC - 20.前台ajax分页