记学习Moq框架(一),上一篇简单讲解了用法,这篇深入讲解用法

应用场景

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface IProductService
{
void Add(Product product);

Task AddAsync(Product product);

void Delete(string id);

Task DeleteAsync(string id);

Product Get(string id);

Task<Product> GetAsync(string id);
}

Mock方法调用

单次方法调用

1
mock.Setup(p => p.Delete(It.IsAny<string>()));

同时也支持异步方法

1
mock.Setup(p => p.DeleteAsync(It.IsAny<string>()));

多次方法调用

1
2
3
4
mock.SetupSequence(p => p.Get(It.IsAny<string>())
.Returns(new Product(){Name = "618狂欢"})
.Returns(new Product(){Name = "双十一"})
.Returns(new Product(){Name = "年货节"});

也可以调用没有返回值的方法void Delete(string id)

1
2
3
4
mock.SetupSequence(p => p.Delete(It.IsAny<string>())
.Pass()
.Pass()
.Pass();

序列调用

由于多个方法按照序列调用,最好采用MockBehavior.Strict严格模式

  • 同个对象,同个方法
1
2
3
4
5
6
var mock = new Mock<IProductService>(MockBehavior.Strict);

var sequence = new MockSequence();

mock.InSequence(sequence).Setup(p => p.Delete("1"));
mock.InSequence(sequence).Setup(p => p.Delete("2"));
  • 同个对象,不同方法
1
2
3
4
5
6
var mock = new Mock<IProductService>(MockBehavior.Strict);

var sequence = new MockSequence();

mock.InSequence(sequence).Setup(p => p.DoWithString(It.IsAny<string>()));
mock.InSequence(sequence).Setup(p => p.DoWithInteger(It.IsAny<int>()));
  • 不同对象
1
2
3
4
5
6
7
var mockFirst = new Mock<IProductService>(MockBehavior.Strict);
var mockSecond = new Mock<IProductRepository>(MockBehavior.Strict);

var sequence = new MockSequence();

mockFirst.InSequence(sequence).Setup(p => p.Delete("1"));
mockSecond.InSequence(sequence).Setup(p => p.Get("1"));

Mock方法参数

指定对象方法的参数类型

1
mock.Setup(p => p.Get(It.IsAny<string>()));

指定对象方法的参数值

1
mock.Setup(p => p.Get("1911112346550025"));

正则表达式匹配It.IsRegex

1
mock.Setup(p => p.Get(It.IsRegex("[A-Z_]+")));

两数区间It.IsInRange

1
mock.Setup(p => p.GetById(It.IsInRange(0, 100, Moq.Range.Exclusive)));

指定返回参数类型的表达式It.Is

1
mock.Setup(p => p.Get(It.Is<string>(s => s.StartsWith("19111"))));

ref 参数

1
mock.Setup(p => p.DoSomething(ref It.Ref<string>.IsAny));

指定引用参数

1
2
var value = "This is a test value";
mock.Setup(p => p.DoSomething(ref value));

调用方法后执行回调方法

1
2
3
4
5
delegate void DoSomethingCallback(ref string value);

var newValue = "This is the new referenced value";
mock.Setup(p => p.DoSomething(It.Ref<string>.IsAny))
.Callback(new DoSomethingCallback((ref string value) => value = newValue));

out 参数

1
2
var value = "This is the expected out value";
mock.Setup(p => p.DoSomething(out value));

可选参数

1
2
3
4
5
6
7
public void DoSomething(string operation = "DEFAULT_OPERATION") { }

// call with custom value
service.DoSomething("MY_CUSTOM_OPERATION");

// call with default value
service.DoSomething();

异步方法调用通常会需要CancellationToken作为入参,给一个默认值default即可

1
2
mock.Setup(p => p.DoSomethingAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()));
mock.Setup(p => p.DoSomethingAsync(It.IsAny<string>(), default));

Mock方法返回

异步方法返回

1
2
mock.Setup(p => p.GetValueAsync()).Returns(Task.FromResult(123));
mock.Setup(p => p.GetValueAsync()).ReturnsAsync(123);

通过委托方法执行返回

1
2
3
4
5
mock.Setup(p => p.Add(It.IsAny<int>(), It.IsAny<int>())
.Returns((int first, int second) => first + second);

mock.Setup(p => p.AddAsync(It.IsAny<int>(), It.IsAny<int>())
.ReturnsAsync((int first, int second) => first + second);

返回null

1
2
var mock = new Mock<Product>();
mock.Setup(p => p.Img).Returns(null as string);

Mock方法回调

可以在方法后面链接Callback,来触发自定义的代码

1
2
var mock = new Mock<IProductService>();
mock.Setup(p => p.Delete()).Callback(() => TestContext.Progress.Writeline("Here"));

无入参的Action

1
mock.Setup(p => p.Delete(It.IsAny<string>())).Callback(() => TestContext.Progress.Writeline($"Incoming call"));

也可以带入方法参数

1
mock.Setup(p => p.Delete(It.IsAny<string>())).Callback((string id) => TestContext.Progress.Writeline($"Incoming call: {id}"));

属性方法

1
2
3
4
5
6
7
var mock = new Mock<Product>();

mock.SetupGet(p => p.Img)
.Callback(() => TestContext.Progress.Writeline("Getter invoked"));

mock.SetupSet(p => p.Img = It.IsAny<string>())
.Callback((string img) => TestContext.Progress.Writeline($"Setter received value: {img}"));

中途变更状态值

1
2
3
4
5
string value = string.Empty;
var mock = new Mock<IProductService>();

mock.Setup(p => p.Delete(It.IsAny<string>()))
.Callback((string id) => value = id);

特定条件

1
2
int counter = 0;
mock.Setup(p => p.DoSomething()).Callback(() => { if (counter++ >= 5) throw new Exception(); });

链接多个委托

1
2
3
4
5
mock.Setup(p => p.DoSomething(It.IsAny<int>())).Callback((int value) => 
{
DoSomething();
DoSomethingWithValue(value)
});

抛出异常

1
2
3
4
var mock = new Mock<IProductService>();

mock.Setup(p => p.Delete(It.IsAny<string>()))
.Callback(() => throw new Exception());

Throws语法糖

1
2
3
4
5
6
7
8
mock.Setup(p => p.Delete(It.IsAny<string>()))
.Throws(new Exception("My custom exception"));

mock.Setup(p => p.Delete(It.IsAny<string>()))
.ThrowsAsync(new Exception("My custom exception"));
//简写
mock.Setup(p => p.Delete((It.IsAny<string>()))
.Throws<Exception>();

序列调用

1
2
3
4
5
6
7
mock.SetupSequence(p => p.GetSomeValue())
.Returns(1)
.Throws<Exception>();

mock.SetupSequence(p => p.GetSomeValueAsync())
.ReturnsAsync(1)
.ThrowsAsync(new Exception());

Mock对象属性

1
2
3
4
var mock = new Mock<Product>();
mock.Setup(p => p.Id).Returns("1");
mock.Setup(p => p.Name).Returns("Bar");
mock.Setup(p => p.Img).Returns("xxx");

也可以对属性的Get、Set方法

1
2
mock.SetupGet(p => p.Name) ... ;
mock.SetupSet(p => p.Name = It.IsAny<string>()) ... ;

属性链返回值

1
2
var mock = new Mock<HttpContextBase>();
mock.SetupGet(p => p.Response.Request.UserAgent).Returns("My Browser");

Mock检查

1
mock.Setup(p => p.Delete(It.IsAny<string>())).Verifiable();

全部检查

1
mock.VerifyAll();

自定义检查失败消息

1
mock.Setup(p => p.Send(It.IsAny<string>())).Verifiable("Send was never invoked");

自选检查mock

1
Mock.Verify(mock, anotherMock, yetAnotherMock);

Mock仓库

1
var mockRepository = new MockRepository(MockBehavior.Strict) { DefaultValue = DefaultValue.Mock };
1
2
var _repository = repository.Create<IProductRepository>();
var _service = repository.Create<IProductService>(MockBehavior.Loose);

Mock深层对象

有时候想直接获得深层对象的mock,Mock.Get对象属性这种方法非常有用

1
var mock = Mock.Get(logger);
1
2
3
4
// 例子
var mock = new Mock<HttpContextBase>();
var context = mock.Object;
var request = Mock.Get(context.Request);

Mock自定义

默认空

1
var mock = new Mock<IProductService> { DefaultValue = DefaultValue.Empty };

模拟数据

1
var mock = new Mock<IProductService> { DefaultValue = DefaultValue.Mock };

自定义默认值提供器

1
2
3
public class MyCustomDefaultValueProvider : DefaultValueProvider { ... }

var mock = new Mock<IProductService> { DefaultValueProvider = new MyCustomDefaultValueProvider() };