Вы можете сделать массу вещей — в зависимости от того, что вы действительно хотите протестировать.
Прежде всего, я хотел бы указать, что большая часть проблем в этом конкретном вопросе возникает из-за чрезвычайно слабо типизированного API IInvocation, а также из-за того, что 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 напрямую
В остальной части моего ответа я предполагаю, что вам действительно нужно присваивать и/или читать значения свойств в ваших тестах.
Прежде всего, вы можете сократить тяжелый синтаксис Moq, назначив ReturnValue напрямую:
[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 создать необходимые значения? И если я это сделаю, мне нужно явно назначать их? Не мог бы я вместо этого просто прочитать присвоенное значение?
Возможно, это связано с небольшой хитростью, которую я называю Типы сигналов. Тип сигнала — это класс, который сигнализирует об определенной роли значения.
Введите тип сигнала для каждого свойства:
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; }
}
}
(Если вам требуется, чтобы значения всегда были строками, вы можете изменить сигнатуру конструктора, чтобы она требовала string
вместо 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 на создание нового экземпляра соответствующего типа сигнала, а затем развертывания его значения.
Этот подход можно обобщить, но в этом его суть.
person
Mark Seemann
schedule
19.12.2012