上一章讲了如何使用Setup
伪造方法
这一章我们将伪造属性和事件
Setup
:伪造属性
上一章我们说过setup
系列不仅可以伪造方法,也可以伪造属性(毕竟属性本质也是方法,┓( ´∀` )┏)。这里依然用上一次的moq
官方文档中的接口为例
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);
event EventHandler MyEvent;
}
public class Bar
{
public virtual Baz Baz { get; set; }
public virtual bool Submit() { return false; }
}
public class Baz
{
public virtual string Name { get; set; }
}
这次不同的是,我们伪造的东西变成了Name
方法。
记得小时候老师教我们做了好事,别人问你叫什么,你就要说“我叫红领巾”。
所以期望IFoo
的Name
方法返回值是”红领巾”.那么就可以写
var fakeFoo = new Mock<IFoo>();
fakeFoo.Setup(fake => fake.Name).Returns("红领巾");
是不是很简单,但是,重点来了,我们还能在API中看到有一个SetupSet
这个方法。
此时,有了经验的童鞋们会说,这不是很简单么,伪造属性的Set
方法。
大错特错
思考下,我们如果伪造了一个属性的Set
方法后,能够干什么呢?只能是验证这个伪对象的属性是否被赋值了。
此时,我们的伪对象作用发生了变化,由Stub
变成了Mock
,
因此,这个命名是非常失败的命名,正确的叫法应该是VerifySet
,当然Moq
也有VerifySet
,他们做的事情也“几乎”
一样。
//使用VerifySet验证
fakeFoo.VerifySet(fake => fake.Name="红领巾");
//使用SetupSet验证
fakeFoo.SetupSet(fake => fake.Name="红领巾");
fakeFoo.VerifyAll();
ok,说了不好的,再说点Moq
厉害的地方——递归伪造
举例说明,你现在期望伪造IFoo
接口的属性Bar
的子属性Baz
的Name
是不是听都听晕了?不急,我们试着写下
//伪造对象
var fakeFoo = new Mock<IFoo>();
var fakeBar = new Mock<Bar>();
var fakeBaz = new Mock<Baz>();
//组装
fakeBaz.Setup(fake => fake.Name).Returns("红领巾");
fakeBar.Setup(fake => fake.Baz).Returns(fakeBaz.Object);
fakeFoo.Setup(fake => fake.Bar).Returns(fakeBar.Object);
可累了是吧,没事有更加简单的写法
var fakeFoo = new Mock<IFoo>();
fakeFoo.Setup(fake => fake.Bar.Baz.Name).Returns("红领巾");
一步到位。递归伪造会将调用路径上的所有对象自动伪造。
因此,这也是区别普通框架和好框架的标准之一。
当然,我们有时候也仅希望伪造一个属性实现,使这个伪造对象可用,
那么就可以使用SetupProperty
添加自动实现
fakeFoo.SetupProperty(fake => fake.Name);
当然也可以设置初始值
fakeFoo.SetupProperty(fake => fake.Name,"红领巾");
好总结下伪造属性的方法。
//伪造属性返回值
fakeFoo.Setup(fake => fake.Name).Returns("红领巾");
//递归伪造
fakeFoo.Setup(fake => fake.Bar.Baz.Name).Returns("红领巾");
//自动属性实现
fakeFoo.SetupProperty(fake => fake.Name,"红领巾");
当然还有要重点区分的
//使用VerifySet验证
fakeFoo.VerifySet(fake => fake.Name="红领巾");
//使用SetupSet验证
fakeFoo.SetupSet(fake => fake.Name="红领巾");
fakeFoo.VerifyAll();
Raise
:伪造事件
事件也是一种常见的依赖,我们常常需要验证在发生某些事件时,被测对象能否顺利响应。
这里的行为偏向于Act
,而之前的那些属于Arrange
至于Arrange
-Act
-Assert
的三A结构,可以参考其他的单元测试书籍。
fakeFoo.Object.MyEvent += OnMyEvent;
fakeFoo.Raise(fack => fack.MyEvent += null, new EventArgs());
很简单,第一个参数请保持为null,因为这个事件永远不会触发,应该监听的是fakeFoo.Object.MyEvent
另外Moq
也支持自定义的EventHandler
类,
而针对泛型版本的EventHandler<T>
,格式会稍稍不同,需要添加sender
fakeFoo.Raise(fack => fack.MyEvent += null, fack, new EventArgs());
本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/Moq%E5%9F%BA%E7%A1%80-%E5%9B%9B.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。