上一章讲了如何使用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方法。

记得小时候老师教我们做了好事,别人问你叫什么,你就要说“我叫红领巾”。

所以期望IFooName方法返回值是”红领巾”.那么就可以写

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的子属性BazName

是不是听都听晕了?不急,我们试着写下

//伪造对象
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 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系