Moq - 基础回顾

目录(?)[+]


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.ReturnsAsyncsetup.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()#

  1. 作用

SetupAllProperties() 的作用是为模拟对象的所有属性自动设置默认的 getter 和 setter 行为。这意味着你可以在测试中直接访问和修改这些属性的值,而无需显式地为每个属性调用 SetupSetupProperty

  1. 示例

假设你有一个接口 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
  1. 特点
  • 自动设置:无需手动为每个属性调用 SetupProperty,可以一次性为所有属性设置默认行为。

  • 简化配置:当需要为多个属性设置行为时,SetupAllProperties() 可以节省代码量,避免重复调用 SetupProperty

  • 支持对属性的读写操作:设置后,你可以像操作普通属性一样对模拟对象的属性进行读写操作。

  1. 注意事项
  • SetupProperty 的区别SetupProperty 需要逐个为属性设置行为,而 SetupAllProperties() 是自动设置所有属性的行为。

  • 属性值的记录:虽然 SetupAllProperties() 允许你操作属性,但它不会像 SetupProperty 那样自动记录属性的值变化。如果需要记录属性值,可以配合 Callback 或其他方法来实现。

总结

SetupAllProperties() 是一个便捷的方法,用于自动为模拟对象的所有属性设置默认的 getter 和 setter 行为。它可以简化代码,减少重复调用,适合需要快速设置多个属性测试场景。

Event 事件#

这段代码是关于使用 Moq 框架来模拟和测试事件行为的示例。以下是对代码中各个部分的解释:

1. SetupAddSetupRemove#

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,都将触发设置为默认行为。

  • 用途:通过 SetupAddSetupRemove,可以精确控制事件订阅和退订的行为,例如触发某些行为或验证事件是否正确地被订阅或退订。

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 是一个自定义的事件委托,它接受两个参数(intbool)。

  • Raise 方法:触发 MyEvent 事件,并传递自定义参数 25true

  • 用途:即使事件不遵循标准的 EventHandler 模式,Raise 方法仍然可以模拟其行为。

5. 总结#

  • SetupAddSetupRemove:用于精确控制事件的订阅和退订行为。

  • 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:捕获方法调用参数,方便后续断言或检查。

  • Refout 参数:需要使用 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:如果设置 CallBasetrue,当没有为某个成员设置期望时,模拟对象将调用基类的实现。这种方式在模拟 Web 或 HTML 控件时特别有用。
var mock = new Mock<IFoo> { CallBase = true };
  • 这行代码创建了一个部分模拟对象,允许调用基类的方法。

3. 自动递归模拟#

  • 自动递归模拟:设置 DefaultValueDefaultValue.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 可以集中创建和管理所有模拟对象。可以一致地设置 MockBehaviorCallBaseDefaultValue
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 表达式提供了一种简洁、直观的方式来定义模拟对象的行为。虽然它的语法可能需要一些时间来熟悉,但在处理复杂的模拟场景时,它可以显著提高代码的可读性和可维护性。

作者:【唐】三三

出处:https://www.cnblogs.com/tangge/p/18709774

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   【唐】三三  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
历史上的今天:
2022-02-11 (6.1)分栏布局
2015-02-11 EasyUi–8.datebox赋值的问题
2015-02-11 MVC - 20.前台ajax分页
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示