AutoFixture、xUnit:设置 TypeRelays 并在构造函数中注入服务
AutoFixture, xUnit: Setup TypeRelays and inject service in constructor
我正在尝试编写一些测试,我使用 xUnit.net、Moq、AutoFixture。我需要为我的测试方法注入服务:
[Theory, AutoData]
public void TestSmthCool(IService service)
{
}
IService
有 3 个我想模拟的依赖项。但是,如果我 运行 测试我得到错误:
AutoFixture was unable to create an instance from Services.Interfaces.IService because it's an interface.
所以,我通过以下方式修复它:
[Theory, AutoData]
public void TestSmthCool()
{
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
fixture.Customizations.Add(
new TypeRelay(
typeof(IService),
typeof(MyService)
)
);
var s= fixture.Create<IService>();
}
但是,如何为所有测试设置 TypeRelay
并通过方法构造函数注入服务?
如果你想用MyService
代替IService
,那么你不需要AutoMoqCustomization
;即此测试通过:
[Fact]
public void TestSmthCool()
{
var fixture = new Fixture();
fixture.Customizations.Add(
new TypeRelay(
typeof(IService),
typeof(MyService)
)
);
var s = fixture.Create<IService>();
Assert.IsAssignableFrom<MyService>(s);
}
如果你想自动执行此操作,你可以先将 TypeRelay
打包到 ICustomization
:
public class MyServiceCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new TypeRelay(
typeof(IService),
typeof(MyService)));
}
}
然后创建一个派生自 AutoDataAttribute
:
的属性
public class MyServiceAutoDataAttribute : AutoDataAttribute
{
public MyServiceAutoDataAttribute() :
base(new Fixture().Customize(new MyServiceCustomization()))
{
}
}
然后您可以在所有测试中使用它:
[Theory, MyServiceAutoData]
public void CustomizedAutoDataBasedTest(IService s)
{
Assert.IsAssignableFrom<MyService>(s);
}
一般来说,我倾向于创建一个代码库范围 CompositeCustomization
,我不加区别地应用于所有测试。
我只是能够通过将 interface/concrete class 实现包装在一个参数属性中来做到这一点,该参数属性添加了一个自定义项,该自定义项又创建了一个 TypeRelay
请参阅下面的示例(顺便说一句,它在 .net 6 中)
public interface IService
{
string Echo(string val);
}
public class MyService : IService
{
public string Echo(string val)
{
return val + "Example";
}
}
public interface IService2
{
string Echo(string val);
}
public class MyService2 : IService2
{
public string Echo(string val)
{
return val + "Example2";
}
}
public sealed class InterfaceMapCustomization : ICustomization
{
private readonly Type _interfaceType;
private readonly Type _concreteType;
public InterfaceMapCustomization(Type interfaceType, Type concreteType)
{
if (!interfaceType.IsAssignableFrom(concreteType))
{
throw new ArgumentException($"Type '{concreteType.Name}' does not implement interface '{interfaceType.Name}'");
}
_interfaceType = interfaceType;
_concreteType = concreteType;
}
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new TypeRelay(_interfaceType, _concreteType));
}
}
/// <summary>
/// Adds a TypeRelay to the Fixture customizations for the specified interface/class pair
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class InterfaceMapAttribute : CustomizeAttribute
{
private readonly Type _interfaceType;
private readonly Type _concreteType;
/// <summary>
/// Adds a TypeRelay to the Fixture customizations for the specified interface/class pair
/// </summary>
/// <param name="interfaceType">The interface to which we want to map our type to</param>
/// <param name="concreteType">The class implementing the interface we want to create</param>
/// <exception cref="InvalidOperationException"></exception>
public InterfaceMapAttribute(Type interfaceType, Type concreteType)
{
if (!interfaceType.IsAssignableFrom(concreteType))
{
throw new InvalidOperationException($"Type '{concreteType.Name}' does not implement interface '{interfaceType.Name}'");
}
_interfaceType = interfaceType;
_concreteType = concreteType;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
if (parameter == null)
{
throw new InvalidOperationException("Parameter info is null");
}
if (parameter.ParameterType != _interfaceType)
{
throw new InvalidOperationException($"Parameter '{parameter.Name}' does not implement interface '{_interfaceType.Name}'");
}
return new CompositeCustomization(new List<ICustomization>
{
new InterfaceMapCustomization(_interfaceType, _concreteType)
});
}
}
public class UnitTest1
{
[Theory]
[AutoData]
public void TestSomething(
string expected,
[InterfaceMap(typeof(IService), typeof(MyService))] IService sut,
[InterfaceMap(typeof(IService2), typeof(MyService2))] IService2 sut2
)
{
var result = sut.Echo(expected);
Assert.Equal(expected + "Example", result);
var result2 = sut2.Echo(expected);
Assert.Equal(expected + "Example2", result2);
}
}
我正在尝试编写一些测试,我使用 xUnit.net、Moq、AutoFixture。我需要为我的测试方法注入服务:
[Theory, AutoData]
public void TestSmthCool(IService service)
{
}
IService
有 3 个我想模拟的依赖项。但是,如果我 运行 测试我得到错误:
AutoFixture was unable to create an instance from Services.Interfaces.IService because it's an interface.
所以,我通过以下方式修复它:
[Theory, AutoData]
public void TestSmthCool()
{
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
fixture.Customizations.Add(
new TypeRelay(
typeof(IService),
typeof(MyService)
)
);
var s= fixture.Create<IService>();
}
但是,如何为所有测试设置 TypeRelay
并通过方法构造函数注入服务?
如果你想用MyService
代替IService
,那么你不需要AutoMoqCustomization
;即此测试通过:
[Fact]
public void TestSmthCool()
{
var fixture = new Fixture();
fixture.Customizations.Add(
new TypeRelay(
typeof(IService),
typeof(MyService)
)
);
var s = fixture.Create<IService>();
Assert.IsAssignableFrom<MyService>(s);
}
如果你想自动执行此操作,你可以先将 TypeRelay
打包到 ICustomization
:
public class MyServiceCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new TypeRelay(
typeof(IService),
typeof(MyService)));
}
}
然后创建一个派生自 AutoDataAttribute
:
public class MyServiceAutoDataAttribute : AutoDataAttribute
{
public MyServiceAutoDataAttribute() :
base(new Fixture().Customize(new MyServiceCustomization()))
{
}
}
然后您可以在所有测试中使用它:
[Theory, MyServiceAutoData]
public void CustomizedAutoDataBasedTest(IService s)
{
Assert.IsAssignableFrom<MyService>(s);
}
一般来说,我倾向于创建一个代码库范围 CompositeCustomization
,我不加区别地应用于所有测试。
我只是能够通过将 interface/concrete class 实现包装在一个参数属性中来做到这一点,该参数属性添加了一个自定义项,该自定义项又创建了一个 TypeRelay
请参阅下面的示例(顺便说一句,它在 .net 6 中)
public interface IService
{
string Echo(string val);
}
public class MyService : IService
{
public string Echo(string val)
{
return val + "Example";
}
}
public interface IService2
{
string Echo(string val);
}
public class MyService2 : IService2
{
public string Echo(string val)
{
return val + "Example2";
}
}
public sealed class InterfaceMapCustomization : ICustomization
{
private readonly Type _interfaceType;
private readonly Type _concreteType;
public InterfaceMapCustomization(Type interfaceType, Type concreteType)
{
if (!interfaceType.IsAssignableFrom(concreteType))
{
throw new ArgumentException($"Type '{concreteType.Name}' does not implement interface '{interfaceType.Name}'");
}
_interfaceType = interfaceType;
_concreteType = concreteType;
}
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new TypeRelay(_interfaceType, _concreteType));
}
}
/// <summary>
/// Adds a TypeRelay to the Fixture customizations for the specified interface/class pair
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class InterfaceMapAttribute : CustomizeAttribute
{
private readonly Type _interfaceType;
private readonly Type _concreteType;
/// <summary>
/// Adds a TypeRelay to the Fixture customizations for the specified interface/class pair
/// </summary>
/// <param name="interfaceType">The interface to which we want to map our type to</param>
/// <param name="concreteType">The class implementing the interface we want to create</param>
/// <exception cref="InvalidOperationException"></exception>
public InterfaceMapAttribute(Type interfaceType, Type concreteType)
{
if (!interfaceType.IsAssignableFrom(concreteType))
{
throw new InvalidOperationException($"Type '{concreteType.Name}' does not implement interface '{interfaceType.Name}'");
}
_interfaceType = interfaceType;
_concreteType = concreteType;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
if (parameter == null)
{
throw new InvalidOperationException("Parameter info is null");
}
if (parameter.ParameterType != _interfaceType)
{
throw new InvalidOperationException($"Parameter '{parameter.Name}' does not implement interface '{_interfaceType.Name}'");
}
return new CompositeCustomization(new List<ICustomization>
{
new InterfaceMapCustomization(_interfaceType, _concreteType)
});
}
}
public class UnitTest1
{
[Theory]
[AutoData]
public void TestSomething(
string expected,
[InterfaceMap(typeof(IService), typeof(MyService))] IService sut,
[InterfaceMap(typeof(IService2), typeof(MyService2))] IService2 sut2
)
{
var result = sut.Echo(expected);
Assert.Equal(expected + "Example", result);
var result2 = sut2.Echo(expected);
Assert.Equal(expected + "Example2", result2);
}
}