There are tons of things you can do - depending on exactly what it is that you really want to test.
First of all I would like to point out that much of the trouble in this particular question originates in the extremely weakly typed API of IInvocation, as well as the fact that Moq doesn't implement properties as we normally implement properties.
Don't setup stubs if you don't need them
First of all, you don't have to setup return values for the Proxy and ReturnValue properties if you don't need them.
The way AutoFixture.AutoMoq sets up Mock<T>
instances is that it always sets DefaultValue = DefaultValue.Mock
. Since the return type of both properties is object
and object
has a default constructor, you will automatically get an object (actually, an ObjectProxy
) back.
In other words, these tests also pass:
[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
}
Directly assign ReturnValue
For the rest of my answer, I'm going to assume that you actually need to assign and/or read the property values in your tests.
First of all, you can cut down on the heavy Moq syntax by assigning the ReturnValue directly:
[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);
}
However, it only works for ReturnValue
since it's a writable property. It doesn't work with the Proxy
property because it's read-only (it's not going to compile).
In order to make this work, you must instruct Moq to treat IInvocation
properties as 'real' properties:
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;
});
}
}
}
Notice the call to SetupAllProperties
.
This works because AutoFixture.AutoMoq works by relaying all requests for interfaces to a request for a Mock of that interface - i.e. a request for IInvocation
is converted to a request for Mock<IInvocation>
.
Don't set the test values; read them back
In the end, you should ask yourself: Do I really need to assign specific values (such as "a", "b" and "z") to these properties. Couldn't I just let AutoFixture create the required values? And if I do that, do I need to explicitly assign them? Couldn't I just read back the assigned value instead?
This is possibly with a little trick I call Signal Types. A Signal Type is a class that signals a particular role of a value.
Introduce a signal type for each property:
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; }
}
}
(If you require the values to always be strings, you can change the constructor signature to require a string
instead of an object
.)
Freeze the Signal Types you care about so that you know the same instance is going to be reused when the IInvocation instance is configured:
[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);
}
The beauty of this approach is that in those test cases where you don't care about the ReturnValue
or Proxy
you can just omit those method arguments.
The corresponding Customization is an expansion of the previous:
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;
});
}
}
}
Notice the that value for each property is assigned by asking the IFixture instance to create a new instance of the corresponding Signal Type and then unwrapping its value.
This approach can be generalized, but that's the gist of it.