我们前面说过.Moq在创建模拟对象的时候,简单对象赋值默认值,引用对象赋值为null,但是有些时候接口里面还包含另一个接口对象,我们知道Moq是可以模拟一个接口对象的,我们可以通过配置让Moq模拟所有可以Mock的对象.
我们新增如下代码
public interface IDtoWrapper
{
MyDto Dto { get; set; }
string GetString();
}
public interface IOutString
{
IDtoWrapper wrapper { get; set; }
}
比如我们要模拟一个IOutString对象,如果不使用默认的行为则里面的wrapper对象返回为Null,如果这样我们没法再继续操作了.
我们可以对Moq的Default
行为进行配置,让它对IDtoWrapper对象也进行Mock
测试代码如下
[Test]
public void BehaviorConfig()
{
var moq = new Mock<IOutString>();
moq.DefaultValue = DefaultValue.Mock;
Assert.NotNull(moq.Object.wrapper);
}
以上测试会通过.这时候wrapper不再是null,我们便可以对其进行操作了.
我们通过调试可以发现这时候不但IDtoWrapper不再是null,它里面的Dto属性也被赋值为一个new MyDto.是不是Mock框架可以模拟一个对象呢,实际上确实是可以,只不过是它模拟对象有很多限制,比如不能模拟不包含无参构造函数的对象,不能模拟不带virtual或者abstract的方法等.如果我们对模拟的对象的方法没有virual或者abstract修饰,这时候如果进行setup则会抛出异常.
前面我们讲的如何通过配置使mock自动mock遇到到层级可Mock对象.这一节我们来看另一个问题.
假如有这样一种场:要mock的接口里有一个Name属性,在业务层我们要根据这个Name决定进入switch的不同分支里面,但是回顾前面的章节,我们没有遇到这种情况,我们都是只是在mock对象建立时使用setup为要mock的对象的字段设置值.很多人可能会想,可以通过moq对象实体的Object属性把这个对象拿出来,然后改变它的值.我们来看看这样做可行不可行.
我们有以下一个简单接口
public interface ISt
{
string Name { get; set; }
int Age { get; set; }
}
测试方法如下
[Test]
public void BehaviorConfig()
{
var moq = new Mock<ISt>();
var obj = moq.Object;
obj.Name = "baidu";
Assert.NotNull(moq.Object.Name);
}
我们把moq的Object对象赋值给obj,然后通过obj改变Name值.我们断言moq.Object.Name的值不为null,不幸的是,测试没有通过.
按我们理解obj和moq.Object应该是引用类型,所以obj值的改变会引起moq.object值的改变,然而实际情况却是我们一旦把moq.Objectm赋值给了obj,它们之间便脱离了关系.看来这样是行不通的.
如何解决这个问题呢,其实moq实例对象里面有一个SetupProperty方法,我们可以通过它来显示指定哪些属性会被跟踪,如果属性被跟踪,则它的变化就会被记录下来,而不像上面.
[Test]
public void BehaviorConfig()
{
var moq = new Mock<ISt>();
moq.SetupProperty(a => a.Name);
var obj = moq.Object;
obj.Name = "baidu";
Assert.NotNull(moq.Object.Name);
}
我们多加了一行代码,测试便可以通过了.
但是如果属性很多,这样一行一行几乎重复的代码挺烦的,moq实例里面还有一个SetupAllProperties
方法,这样可以设置所有的属性都被跟踪.这样如果多条需要这个值,我们便不需要每次都mock它,而只需要给它重新赋值即可.