'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 |
