DataSourceGeneratorAttribute query with autofixture and frozen constructs #787
Replies: 2 comments
-
I don't think I quite follow fully. Does this not do what you need? [TestServiceGenerator]
public class TestServiceTestsWithSpecialSauce(TestService sut, [Frozen] IOptions<TestOptions> options)
{
[Test]
[Arguments(1, "hello")]
public void Test(int id, string value)
{
var name = sut.Name;
name.Should().Be(options.Value.Name);
}
}
public class TestServiceGeneratorAttribute : DataSourceGeneratorAttribute<TestService, IOptions<TestOptions>>
{
private readonly Fixture _fixture;
public TestServiceGeneratorAttribute()
{
_fixture = new Fixture();
_fixture.Customize(new AutoMoqCustomization());
_fixture.Customizations.Add(new OptionsSpecificationBuilder());
}
public override IEnumerable<(TestService, IOptions<TestOptions>)> GenerateDataSources(DataGeneratorMetadata metadata)
{
var options = metadata.ParameterInfos!.ElementAt(1).ParameterType.GetCustomAttribute<FrozenAttribute>() != null
? _fixture.Freeze<IOptions<TestOptions>>()
: _fixture.Create<IOptions<TestOptions>>();
var testService = _fixture.Create<TestService>();
yield return (testService, options);
}
}
public class OptionsSpecificationBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
// determine if it's what we are looking for
if (request is Type type
&& type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(IOptions<>))
{
// determine the type of options you want to create
var typeOfOptions = type.GetGenericArguments()[0];
// get autofixture to resolve an instance for you using the default creation routines
var instance = context.Resolve(typeOfOptions);
// determine the type and through generics get access to the generic version of the method. You can't call it directly otherwise
// at runtime it behaves like it is an IOptions<object> which is not what you are looking for.
var optionsCreateMethod = typeof(Options).GetMethod(nameof(Options.Create))?.MakeGenericMethod(typeOfOptions);
// call the method, if available, with the instance resolved above. This is equivalent to Options.Create<typeOfOptions>(instance)
var optionsInstance = optionsCreateMethod?.Invoke(null, new[] { instance });
// return if possible, otherwise return no specimen
return optionsInstance ?? new NoSpecimen();
}
return new NoSpecimen();
}
} |
Beta Was this translation helpful? Give feedback.
-
Thanks for taking the time to reply. Conceptually I believe this would work. However as the dependency count got larger or the codebase of items to test increased, if I've understood the solution properly, there would end up being a lot of boiler plate code in derived implementations of Using your example as starting point I will continue to play to see what I can come up with and get back to you :-) |
Beta Was this translation helpful? Give feedback.
-
Hi,
I've been playing around with TUnit and finding it pretty cool. Great usage of the new generic attributes etc.
I have however come across a scenario I can't work out how to get around and that is similar construct to the InlineAutoData which uses Autofixture and the xUnit data attributes.
The scenario appears to be a combination of the
ArgumentsAttribute
usage, the method data sources and the DataSourceGenerators<>.Using my recent blog post for examples to work through - https://adamstorr.co.uk/blog/autofixture-and-ioptions-a-winning-combination/ - I have managed to work though all the scenarios until I get to options 6 when it starts to abstract out the AutoFixture setup etc.
I have tried
With the attribute definition
The thought process being I could look at the
dataGeneratorMetadata
and interrogate the details of the ParameterInfo details passed in. But this doesn't work and the test doesn't seem to register in the runner.I then tried an implementation of the DataSourceGenerator:
And this does create the required instantiation of the
TestService
which is good. But I can't extend the dependencies to say I optionally want access to the Frozen dependency so I can reference it in the test scenario.I've had a look at the
IClassConstructor
constructs and can't see how this would fit either.The api surface I am currently thinking would be something like ...
Allows you to specify the class level details for the service under test and reference any class level structures or instantiate items or reference marking a mock as frozen etc. Then at each test scenario in the class you could specify values as required.
It's quite a common pattern for a lot of tests which I write to try and reduce duplication of boilerplate code. Is this a scenario which could be supported? Is there a way today that I can write this?
Thanks again 😎
Beta Was this translation helpful? Give feedback.
All reactions