'AutoFixture, xUnit: Setup TypeRelays and inject service in constructor

I am trying to write some tests, I use xUnit.net, Moq, AutoFixture. I need to inject service to my test method:

[Theory, AutoData]
public void TestSmthCool(IService service)
{
}

The IService has 3 dependencies which I want to mock. But, If I run the test I get error:

AutoFixture was unable to create an instance from Services.Interfaces.IService because it's an interface.

So, I fixed it in the following way:

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

But, how to setup TypeRelay for all tests and inject service via method constructor?



Solution 1:[1]

I was just able to do this by wrapping the interface/concrete class implementation in a parameter attribute that adds a customization which in turn creates a TypeRelay

see example below (its in .net 6 by the way)

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1