你可以做很多事情——这取决于你真正想要测试的是什么。
首先,我想指出,这个特定问题的大部分问题都源于 IInvocation 的类型极弱的 API,以及 Moq 没有像我们通常实现属性那样实现属性这一事实。
如果您不需要存根,请不要设置它们
首先,如果您不需要它们,则不必为 Proxy 和 ReturnValue 属性设置返回值。
AutoFixture.AutoMoq 设置Mock<T>
实例的方式是它总是设置DefaultValue = DefaultValue.Mock
. 由于这两个属性的返回类型都是object
并且object
具有默认构造函数,因此您将自动获取一个对象(实际上是一个ObjectProxy
)。
换句话说,这些测试也通过了:
[Theory, CustomAutoData]
public void TestA2(InterceptorA sut, IInvocation context)
{
sut.Intercept(context);
// assert
}
[Theory, CustomAutoData]
public void TestB2(InterceptorB sut, IInvocation context)
{
sut.Intercept(context);
// assert
}
直接赋值 ReturnValue
对于我的其余答案,我将假设您实际上需要在测试中分配和/或读取属性值。
首先,您可以通过直接分配 ReturnValue 来减少繁重的 Moq 语法:
[Theory, Custom3AutoData]
public void TestA3(InterceptorA sut, IInvocation context)
{
context.ReturnValue = "b";
sut.Intercept(context);
// assert
Assert.Equal("b", context.ReturnValue);
}
[Theory, Custom3AutoData]
public void TestB3(InterceptorB sut, IInvocation context)
{
context.ReturnValue = "z";
sut.Intercept(context);
// assert
Assert.Equal("z", context.ReturnValue);
}
但是,它仅适用于ReturnValue
因为它是可写属性。它不适用于该Proxy
属性,因为它是只读的(它不会编译)。
为了完成这项工作,您必须指示 Moq 将IInvocation
属性视为“真实”属性:
public class Customization3 : CompositeCustomization
{
public Customization3()
: base(
new RealPropertiesOnInvocation(),
new AutoMoqCustomization())
{
}
private class RealPropertiesOnInvocation : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Register<Mock<IInvocation>>(() =>
{
var td = new Mock<IInvocation>();
td.DefaultValue = DefaultValue.Mock;
td.SetupAllProperties();
return td;
});
}
}
}
注意对 的调用SetupAllProperties
。
这是因为 AutoFixture.AutoMoq 的工作原理是将所有接口请求中继到该接口的 Mock 请求 - 即,请求IInvocation
转换为请求Mock<IInvocation>
。
不要设置测试值;读回来
最后,您应该问自己:我真的需要为这些属性分配特定的值(例如“a”、“b”和“z”)吗?我不能让 AutoFixture 创建所需的值吗?如果我这样做,我是否需要明确分配它们?我不能只读回分配的值吗?
这可能是我称之为Signal Types的一个小技巧。信号类型是一个表示值的特定角色的类。
为每个属性引入一个信号类型:
public class InvocationReturnValue
{
private readonly object value;
public InvocationReturnValue(object value)
{
this.value = value;
}
public object Value
{
get { return this.value; }
}
}
public class InvocationProxy
{
private readonly object value;
public InvocationProxy(object value)
{
this.value = value;
}
public object Value
{
get { return this.value; }
}
}
(如果您要求值始终是字符串,您可以将构造函数签名更改为需要 astring
而不是object
.)
冻结您关心的信号类型,以便您知道在配置 IInvocation 实例时将重用相同的实例:
[Theory, Custom4AutoData]
public void TestA4(
InterceptorA sut,
[Frozen]InvocationProxy proxy,
[Frozen]InvocationReturnValue returnValue,
IInvocation context)
{
sut.Intercept(context);
// assert
Assert.Equal(proxy.Value, context.Proxy);
Assert.Equal(returnValue.Value, context.ReturnValue);
}
[Theory, Custom4AutoData]
public void TestB4(
InterceptorB sut,
[Frozen]InvocationReturnValue returnValue,
IInvocation context)
{
sut.Intercept(context);
// assert
Assert.Equal(returnValue.Value, context.ReturnValue);
}
这种方法的美妙之处在于,在那些你不关心的测试用例中,ReturnValue
或者Proxy
你可以忽略那些方法参数。
对应的定制是对前面的扩展:
public class Customization4 : CompositeCustomization
{
public Customization4()
: base(
new RelayedPropertiesOnInvocation(),
new AutoMoqCustomization())
{
}
private class RelayedPropertiesOnInvocation : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Register<Mock<IInvocation>>(() =>
{
var td = new Mock<IInvocation>();
td.DefaultValue = DefaultValue.Mock;
td.SetupAllProperties();
td.Object.ReturnValue =
fixture.CreateAnonymous<InvocationReturnValue>().Value;
td.Setup(i => i.Proxy).Returns(
fixture.CreateAnonymous<InvocationProxy>().Value);
return td;
});
}
}
}
请注意,每个属性的值是通过要求 IFixture 实例创建相应信号类型的新实例然后解包其值来分配的。
这种方法可以概括,但这就是它的要点。