Всегда замораживать издевки с использованием AutoFixture, XUnit и Moq
Я использую расширения AutoFixture, Moq и XUnit (атрибут [Theory]
), как описано в этом сообщении в блоге http://blog.ploeh.dk/2010/10/08/AutoDataTheorieswithAutoFixture.
Я заметил, что большинство модульных тестов выглядит следующим образом:
[Theory, AutoMoqData]
public void Test(
[Frozen] Mock<IServiceOne> serviceOne,
[Frozen] Mock<IServiceTwo> serviceTwo,
MyClass classUnderTest)
{
// Arrange
serviceOne
.Setup(m => m.Get(It.IsAny<int>()));
serviceTwo
.Setup(m => m.Delete(It.IsAny<int>()));
// MyClass has a constructor with arguments for IServiceOne, and IServiceTwo
// classUnderTest will use the two mocks specified above
// Act
var result = classUnderTest.Foo();
// Assert
Assert.True(result);
}
В отличие от того, чтобы всегда украшать издевательства с помощью [Frozen]
, есть ли способ настроить прибор, чтобы всегда замораживать mocks?
Здесь атрибут AutoMoqData
:
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute()
: base(new Fixture().Customize(new AutoMoqCustomization()))
{
}
}
Несмотря на то, что в настоящее время он не встроен, легко написать Декоратор общего назначения, который замораживает объекты, когда они покидают дерево автоматической адаптации:
public class MemoizingBuilder : ISpecimenBuilder
{
private readonly ISpecimenBuilder builder;
private readonly ConcurrentDictionary<object, object> instances;
public MemoizingBuilder(ISpecimenBuilder builder)
{
this.builder = builder;
this.instances = new ConcurrentDictionary<object, object>();
}
public object Create(object request, ISpecimenContext context)
{
return this.instances.GetOrAdd(
request,
r => this.builder.Create(r, context));
}
}
Обратите внимание, что он украшает другой ISpecimenBuilder
, но запоминает все значения перед их возвратом. Если тот же запрос поступит снова, он вернет memoized значение.
Пока вы не можете расширить AutoMoqCustomization
, вы можете реплицировать то, что он делает (это всего две строки кода), и использовать MemoizingBuilder
вокруг него:
public class AutoFreezeMoq : ICustomization
{
public void Customize(IFixture fixture)
{
if (fixture == null)
throw new ArgumentNullException("fixture");
fixture.Customizations.Add(
new MemoizingBuilder(
new MockPostprocessor(
new MethodInvoker(
new MockConstructorQuery()))));
fixture.ResidueCollectors.Add(new MockRelay());
}
}
Используйте AutoFreezeMoq
вместо AutoMoqCustomization
. Он заморозит все mocks и все интерфейсы и абстрактные базовые классы, созданные из этих mocks.
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute()
: base(new Fixture().Customize(new AutoFreezeMoq()))
{
}
}
Я закончил копирование кода из класса AutoDataAttribute
и изменил его, указав FreezingCustomization
.
Это результирующий атрибут.
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute()
: base(new Fixture().Customize(new AutoMoqCustomization()))
{
}
public override IEnumerable<object[]> GetData(System.Reflection.MethodInfo methodUnderTest, Type[] parameterTypes)
{
var specimens = new List<object>();
foreach (var p in methodUnderTest.GetParameters())
{
CustomizeFixture(p);
if (p.ParameterType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IMock<>)))
{
var freeze = new FreezingCustomization(p.ParameterType, p.ParameterType);
this.Fixture.Customize(freeze);
}
var specimen = Resolve(p);
specimens.Add(specimen);
}
return new[] { specimens.ToArray() };
}
private void CustomizeFixture(ParameterInfo p)
{
var dummy = false;
var customizeAttributes = p.GetCustomAttributes(typeof(CustomizeAttribute), dummy).OfType<CustomizeAttribute>();
foreach (var ca in customizeAttributes)
{
var c = ca.GetCustomization(p);
this.Fixture.Customize(c);
}
}
private object Resolve(ParameterInfo p)
{
var context = new SpecimenContext(this.Fixture);
return context.Resolve(p);
}
}