As at AutoFixture 4.8.0, is there a better alternative to using Fixture.Register
to register a function to create a type that exposes only an internal
constructor?
I'm working on a .net standard class library to build API for internal use. We do not want to expose public constructors and as such all constructors for these models are internal
. Properties are additionally public readonly
. These classes will be instantiated through Json deserialization, and in this particular library there is a model with > 20 properties to consider. As a bonus, we'd like to use [AutoDataAttribute] where possible.
ie. is there an alternative to supplying an internal jsonconstructor with a mixture of non and complex types (also internal) with 20+ arguments using AutoFixture?
[TestFixture]
public sealed class Tests
{
private Fixture _fixture;
[OneTimeSetUp]
public void OneTimeSetup()
{
_fixture = new Fixture();
_fixture.Register(() => new Vehicle(_fixture.Create<string>(), _fixture.Create<int>()));
_fixture.Register(
() => new Driver(_fixture.Create<string>(), _fixture.Create<int>(), _fixture.Create<Vehicle>()));
}
[Test]
public void CanCreateDriverWithVehicle()
{
Func<Driver> func = () => _fixture.Create<Driver>();
func.Should().NotThrow(); // Green
}
}
[PublicAPI]
public sealed class Driver
{
public readonly string Name;
public readonly int Age;
public Vehicle Vehicle;
[JsonConstructor]
internal Driver(string name, int age, Vehicle vehicle)
{
Name = name;
Age = age;
Vehicle = vehicle;
}
}
[PublicAPI]
public sealed class Vehicle
{
public readonly string Manufacturer;
public readonly int Miles;
[JsonConstructor]
internal Vehicle(string manufacturer, int miles)
{
Manufacturer = manufacturer;
Miles = miles;
}
}
By default AutoFixture considers the public constructors only. It doesn't pick up the internal constructors, even if they are accessible from the test execution assembly (not even sure whether there is a reasonable technical possibility to check that).
You can easily customize your fixture to support that. Get inspiration from the following code sample:
[Fact]
public void ShouldActivateTypesWithInternalConstructor()
{
var fixture = new Fixture();
fixture.ResidueCollectors.Add(
new Postprocessor(
new MethodInvoker(
new ModestInternalConstructorQuery()),
new AutoPropertiesCommand()
));
var result = fixture.Create<TypeWithInternalConstructor>();
Assert.NotEqual(0, result.Property);
}
public class ModestInternalConstructorQuery : IMethodQuery
{
public IEnumerable<IMethod> SelectMethods(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return from ci in type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)
where ci.IsAssembly // Take internal constructors only
let parameters = ci.GetParameters()
where parameters.All(p => p.ParameterType != type)
orderby parameters.Length ascending
select new ConstructorMethod(ci) as IMethod;
}
}
public class TypeWithInternalConstructor
{
public int Property { get; }
internal TypeWithInternalConstructor(int property)
{
Property = property;
}
}