Replies: 11 comments
-
Seems you are hunting the wrong problem ;) The extension method Create on NumericGenerator does not work like you think. Just write the following code and you will see the excpetion:
It looks a littly ugly, but if you write it this way, it works:
In the end I may ask the question why to use this generator at all? I mean if you want to limit it to a single property of a single type you can do the following:
So the With extension method works fine, as you can see. |
Beta Was this translation helpful? Give feedback.
-
Hi @Ergamon, thanks for your reply. Your snippet works as expected. Thanks. In reality, the question was meant to be a bit more generic because there are many cases where you want to use a specific I suggest looking into the possibility of creating an overload of fixture.Customize<Contact>(c => c
.With(p => p.Id, new NumericSequenceGenerator())
.With(p => p.DateOfBirth, new RandomDateTimeSequenceGenerator(new DateTime(1980, 1, 1), new DateTime(1985, 12, 31)))
); |
Beta Was this translation helpful? Give feedback.
-
As we both know, development here is not too heavy ;) I looked into the source and the problem seems to be that the Create extension generates a SeededRequest request and not a typeof(T) request. NumericSequenceGenerator cannot handle SeededRequest. If you want to have your syntax you can just use an extension like this:
|
Beta Was this translation helpful? Give feedback.
-
oh wow! thanks @Ergamon It worked like a charm. Maybe you could consider packaging it as a PR and let @aivascu decide if it's something to be merged |
Beta Was this translation helpful? Give feedback.
-
@Kralizek this issue reminds me of a similar suggestion #1180, to simplify the specification of As for the specific extensions proposed by @Ergamon, I see an issue in the sense that not every specimen builder accepts the A naive solution, I can suggest is making use of the factory method overload of the var fixture = new Fixture();
fixture.Customize<PropertyHolder>(
x => x.With(x => x.MyProperty, NumericFactory.Create<int>));
/* ... */
public static class NumericFactory
{
public static T Create<T>()
where T : struct, IComparable<T>, IConvertible
{
var specimen = new NumericSequenceGenerator().Create(typeof(T), null);
if (specimen is T value) return value;
throw new InvalidOperationException(
$"Specimen builder {typeof(NumericSequenceGenerator)} could not generate type {typeof(T)}.");
}
} this would allow you to customize the request before it is processed by the specimen builder. Of course this is not ideal as well since some builders require more "context". For example the same public static T FromRange<T>(T min, T max)
where T : struct, IComparable<T>, IConvertible
{
var specimen = new RangedNumberGenerator()
.Create(
new RangedNumberRequest(typeof(T), min, max),
new SpecimenContext(new NumericSequenceGenerator()));
if (specimen is T value) return value;
throw new InvalidOperationException(
$"Specimen builder {typeof(NumericSequenceGenerator)} could not generate type {typeof(T)}.");
} Perhaps there is a better way to approach this problem this is what came up from the top of my head. |
Beta Was this translation helpful? Give feedback.
-
@aivascu |
Beta Was this translation helpful? Give feedback.
-
@Kralizek could you provide an example? I don't think I understand your idea. |
Beta Was this translation helpful? Give feedback.
-
The problem, as I see it, is that I was thinking about introducing parent classes like the one below and modify the existing builders to inherit from these classes. public abstract class TypeSpecimenBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (request is Type type)
{
return Create(type, context);
}
return new NoSpecimen();
}
public abstract object Create(Type request, ISpecimenContext context);
}
public class NumericSequenceGenerator : TypeSpecimenBuilder, ISpecimenBuilder
{
public virtual object Create(Type request, ISpecimenContext context)
{
return this.CreateNumericSpecimen(request);
}
...
} This would allow us to distinguish between families of builders and leverage this knowledge to empower the API like below public static IPostprocessComposer<T> With<T, TProperty>(
this IPostprocessComposer<T> postprocessComposer,
Expression<Func<T, TProperty>> propertyPicker,
TypeSpecimenBuilder specimenBuilder)
{
return postprocessComposer
.With(propertyPicker, specimenBuilder.Create(typeof(TProperty),null));
} |
Beta Was this translation helpful? Give feedback.
-
@Kralizek I see what you mean and I think this solution might work for a certain number of specimen builders. A more generic and probably less intrusive solution could be to customize the properties using a "context-aware" factory method, that would compose a request from any given parameters, either just the type or some additional values, like for example a range of numbers and then just pass the request object to the current context and let the context (fixture) figure out how to resolve the request. fixture.Customize<Customer>(
x => x.With(c => c.Age, s => s.FromRange(18, 89))
.With(c => c.FirstName, s => s.FirstName(locale: "en-GB"))
.With(c => c.Email, s => s.EmailAddress(domain: "example.com"))
); I think that this kind API would be much more expressive and extensible since all the methods on the right-hand side could be extension methods that compose request objects. Same could apply for type builders. fixture.Build<Customer>()
.With(c => c.Age, s => s.FromRange(22, 100))
.With(c => c.FirstName, s => s.FirstName(locale: "de-DE"))
.With(c => c.Email, s => s.EmailAddress(domain: "foo.co.uk"))
.Create(); |
Beta Was this translation helpful? Give feedback.
-
Interesting idea! @aivascu Could you sketch down the implementation of one of these extension methods? |
Beta Was this translation helpful? Give feedback.
-
@Kralizek sorry for the delay. As it turns out the behavior I've described is already possible using for example value factory override of the [Fact]
public void BuildShouldInvoke()
{
var fixture = new Fixture();
var holder = fixture.Build<PropertyHolder<int>>()
.With<int, IFixture>(x => x.Property, (x) => x.FromRange(538, 827))
.CreateMany(15);
Assert.True(holder.All(x => x.Property >= 538 && x.Property <= 827));
}
public static class RequestExtensions
{
public static T FromRange<T>(this ISpecimenBuilder builder, T min, T max)
{
return (T)builder.Create(
new RangedNumberRequest(typeof(T), min, max),
new SpecimenContext(builder));
}
} Assuming you have customized beforehand the By adding an overload for the [Fact]
public void BuildShouldInvoke1()
{
var fixture = new Fixture();
var holder = fixture.Build<PropertyHolder<int>>()
.With(x => x.Property, (x) => x.FromRange(15, 20))
.CreateMany(15);
Assert.True(holder.All(x => x.Property >= 15 && x.Property <= 20));
} |
Beta Was this translation helpful? Give feedback.
-
Hi,
I have a small type like the following one:
I'd like to configure my fixture to use the
NumericSequenceGenerator
on theId
property.I know that when customizing a type,
FromFactory
accepts aISpecimenBuilder
but this is not the same forWith
.Ideally, I'd like to do something like the snippet below, but I don't think it's supported.
I even tried with using the delegate overload
but it throws an exception:
Right now I'm using
which is equivalent to
The issue with this approach is that every integer is affected
Beta Was this translation helpful? Give feedback.
All reactions