You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Default BuildObject implementation using reflection #24
Automatically build an object by reflecting for a constructor with the most params and getting each by looking for a property of the same name (kinda like AutoFixture, but this would use the ability to customise the object(s).
This would be the default behaviour for BuildObject - you can then override it as soon as you get a more complex example.
This just makes it even more easy to quickly create a builder.
Might be more complex than it's worth though? It doesn't take long to create the BuildObject method...
I've implemented this as an extension method. There's a bit of ninja backflipping to get the data out of the builder when running outside of the builder but that's easily fixed when implementing this for realsies.
publicstaticclassTestDataBuilderExtensions{publicstaticTObjectBuildObjectByConstructor<TObject,TBuilder>(thisTestDataBuilder<TObject,TBuilder>builder)whereTObject:classwhereTBuilder:TestDataBuilder<TObject,TBuilder>,new(){varlongestConstructor=typeof(TObject).GetConstructors().OrderByDescending(x =>x.GetParameters().Length).FirstOrDefault();if(longestConstructor==null)thrownewObjectCreationException();// TODO Validation around Parameters.All(x => typeof(TObject).HasProperty(...))varparameterValues=longestConstructor.GetParameters().Select(x =>new{PropertyName=x.Name.ToUpperCamelCase(),PropertyType=x.ParameterType}).Select(x =>GetValueStoredInBuilder(builder,x.PropertyType,x.PropertyName));return(TObject)longestConstructor.Invoke(parameterValues.ToArray());}publicstaticstringToUpperCamelCase(thisstringparam){if(string.IsNullOrWhiteSpace(param))thrownewArgumentNullException("param");varbuilder=newStringBuilder(param);builder[0]=char.ToUpperInvariant(builder[0]);returnbuilder.ToString();}publicstaticboolHasProperty(thisTypetype,stringpropertyName,TypepropertyType){varpropertyInfo=type.GetProperty(propertyName);if(propertyInfo==null)returnfalse;returnpropertyInfo.PropertyType==propertyType;}privatestaticobjectGetValueStoredInBuilder<TObject,TBuilder>(TestDataBuilder<TObject,TBuilder>builder,TypepropertyType,stringpropertyName)whereTObject:classwhereTBuilder:TestDataBuilder<TObject,TBuilder>,new(){// Make a Func<TObj, TPropertyType>varexpressionDelegateType=typeof(Func<,>).MakeGenericType(typeof(TObject),propertyType);// Make an expression parameter of type TObjvartObjParameterType=Expression.Parameter(typeof(TObject));varvalueStoredInBuilder=typeof(TBuilder).GetMethod("Get").MakeGenericMethod(propertyType).Invoke(builder,newobject[]{Expression.Lambda(expressionDelegateType,Expression.Property(tObjParameterType,propertyName),tObjParameterType)});returnvalueStoredInBuilder;}}
There are obvious shortcomings to this approach:
Constructor argument names must match Property Names exactly, bar the lower-upper camel casing.
There must be a 1-1 relationship to constructor args - properties.
Perhaps this feature could be opt-in by making it a protected method rather than a default?
My thinking was if we couldn't find a matching value via Get that we would then use the anonymous value stuff :) and in fact, the Get method does this for us automatically anyway.
Case sensitivity might actually be an issue because we store the values in a dictionary, we might need to make a chance to make it case insensitive by lowercasing the key when it gets popped in and when it getschecked.
To avoid having to implement GetValueStoredInBuilder I think you'll need a private overload of Get that takes a String anyway, so the current version could proxy to the overload and the overload could handle the case sensitivity.
FYI I'm not using this personally, I just had a similar implementation to populate a DTO via setters and figured I should create a basic version while it was still in my head. :)
I ended up using my reflective method to call Get<TValue> as I didn't want to pollute the entire generics stack with untyped stuff just for this one use case. We would need to change the whole chain of AnonymouseValueFixture.Get<TObject, T>, IAnonymousValueSupplier.CanSupplyValue and IAnonymousValueSupplier.GenerateAnonymousValue.
I'm not convinced this is a good solution, so perhaps this was a decent exercise in proving it is "too much effort" for the amount of value.
Activity
Bringer128 commentedon May 1, 2015
I've implemented this as an extension method. There's a bit of ninja backflipping to get the data out of the builder when running outside of the builder but that's easily fixed when implementing this for realsies.
There are obvious shortcomings to this approach:
Perhaps this feature could be opt-in by making it a protected method rather than a default?
robdmoore commentedon May 1, 2015
Hi Ryan :)
Nice one!
My thinking was if we couldn't find a matching value via
Getthat we would then use the anonymous value stuff :) and in fact, the Get method does this for us automatically anyway.Case sensitivity might actually be an issue because we store the values in a dictionary, we might need to make a chance to make it case insensitive by lowercasing the key when it gets popped in and when it gets checked.
Bringer128 commentedon May 1, 2015
To avoid having to implement
GetValueStoredInBuilderI think you'll need a private overload ofGetthat takes aStringanyway, so the current version could proxy to the overload and the overload could handle the case sensitivity.FYI I'm not using this personally, I just had a similar implementation to populate a DTO via setters and figured I should create a basic version while it was still in my head. :)
Bringer128 commentedon May 1, 2015
I ended up using my reflective method to call
Get<TValue>as I didn't want to pollute the entire generics stack with untyped stuff just for this one use case. We would need to change the whole chain ofAnonymouseValueFixture.Get<TObject, T>,IAnonymousValueSupplier.CanSupplyValueandIAnonymousValueSupplier.GenerateAnonymousValue.I'm not convinced this is a good solution, so perhaps this was a decent exercise in proving it is "too much effort" for the amount of value.