Skip to content

上一章讲了如何使用Setup伪造方法

这一章我们将伪造属性和事件

Setup:伪造属性

上一章我们说过setup系列不仅可以伪造方法,也可以伪造属性(毕竟属性本质也是方法,┓( ´∀` )┏)。这里依然用上一次的moq官方文档中的接口为例

c#
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方法返回值是"红领巾".那么就可以写

c#
var fakeFoo = new Mock<IFoo>();
fakeFoo.Setup(fake => fake.Name).Returns("红领巾");

是不是很简单,但是,重点来了,我们还能在API中看到有一个SetupSet这个方法。

此时,有了经验的童鞋们会说,这不是很简单么,伪造属性的Set方法。

大错特错

思考下,我们如果伪造了一个属性的Set方法后,能够干什么呢?只能是验证这个伪对象的属性是否被赋值了。

此时,我们的伪对象作用发生了变化,由Stub变成了Mock ,

因此,这个命名是非常失败的命名,正确的叫法应该是VerifySet ,当然Moq也有VerifySet ,他们做的事情也“几乎”一样。

c#
//使用VerifySet验证
fakeFoo.VerifySet(fake => fake.Name="红领巾");
//使用SetupSet验证
fakeFoo.SetupSet(fake => fake.Name="红领巾");
fakeFoo.VerifyAll();

ok,说了不好的,再说点Moq厉害的地方——递归伪造

举例说明,你现在期望伪造IFoo接口的属性Bar的子属性BazName

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

c#
//伪造对象
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);

可累了是吧,没事有更加简单的写法

c#
var fakeFoo = new Mock<IFoo>();
fakeFoo.Setup(fake => fake.Bar.Baz.Name).Returns("红领巾");

一步到位。递归伪造会将调用路径上的所有对象自动伪造。

因此,这也是区别普通框架和框架的标准之一。

当然,我们有时候也仅希望伪造一个属性实现,使这个伪造对象可用,

那么就可以使用SetupProperty添加自动实现

c#
fakeFoo.SetupProperty(fake => fake.Name);

当然也可以设置初始值

c#
fakeFoo.SetupProperty(fake => fake.Name,"红领巾");

好总结下伪造属性的方法。

c#
//伪造属性返回值
fakeFoo.Setup(fake => fake.Name).Returns("红领巾");
//递归伪造
fakeFoo.Setup(fake => fake.Bar.Baz.Name).Returns("红领巾");
//自动属性实现
fakeFoo.SetupProperty(fake => fake.Name,"红领巾");

当然还有要重点区分的

c#
//使用VerifySet验证
fakeFoo.VerifySet(fake => fake.Name="红领巾");
//使用SetupSet验证
fakeFoo.SetupSet(fake => fake.Name="红领巾");
fakeFoo.VerifyAll();

Raise:伪造事件

事件也是一种常见的依赖,我们常常需要验证在发生某些事件时,被测对象能否顺利响应。

这里的行为偏向于Act,而之前的那些属于Arrange

至于Arrange-Act-Assert的三A结构,可以参考其他的单元测试书籍。

c#
fakeFoo.Object.MyEvent += OnMyEvent;
fakeFoo.Raise(fack => fack.MyEvent += null, new EventArgs());

很简单,第一个参数请保持为null,因为这个事件永远不会触发,应该监听的是fakeFoo.Object.MyEvent

另外Moq也支持自定义的EventHandler类,

而针对泛型版本的EventHandler&lt;T&gt;,格式会稍稍不同,需要添加sender

c#
fakeFoo.Raise(fack => fack.MyEvent += null, fack, new EventArgs());

最后更新于:

基于 VitePress + @sugarat/theme 构建