3

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?

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
user348173
  • 8,818
  • 18
  • 66
  • 102

2 Answers2

3

If you wish to use MyService for IService, then you don't need AutoMoqCustomization; i.e. this test passes:

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

If you want to automate this, you can first package the TypeRelay in an ICustomization:

public class MyServiceCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(
           new TypeRelay(
              typeof(IService),
              typeof(MyService)));
    }
}

Then create an attribute that derives from AutoDataAttribute:

public class MyServiceAutoDataAttribute : AutoDataAttribute
{
    public MyServiceAutoDataAttribute() :
        base(new Fixture().Customize(new MyServiceCustomization()))
    {
    }
}

You can then use that in all your tests:

[Theory, MyServiceAutoData]
public void CustomizedAutoDataBasedTest(IService s)
{
    Assert.IsAssignableFrom<MyService>(s);
}

In general, I tend to create a code base-wide CompositeCustomization that I apply indiscriminately to all tests.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
0

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);
    }
}
Ilias.P
  • 179
  • 7