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);
    }
}