Last active
August 29, 2015 14:01
-
-
Save quexy/98c4ef7ec6bf693031c8 to your computer and use it in GitHub Desktop.
Object verifier for SpecFlow
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq.Expressions; | |
namespace TechTalk.SpecFlow.ObjectVerification | |
{ | |
public static class ObjectVerifierExtensions | |
{ | |
/// <summary> | |
/// Specifies the aliases in the verification data for the given property of the object. There can be more than one alias for any property, but not vice versa. | |
/// </summary> | |
public static IConfiguredObjectVerifier<TObject> WithPropertyAlias<TObject, TProperty>(this IConfiguredObjectVerifier<TObject> objectVerifier, Expression<Func<TObject, TProperty>> selector, params string[] aliases) | |
{ | |
foreach (var alias in aliases) | |
objectVerifier.WithPropertyAlias(selector, alias); | |
return objectVerifier; | |
} | |
/// <summary> | |
/// Specifies the equality comparer to use when verifying the given field mapped to the verified object (comparison priority: table field, object property, target value, default) | |
/// </summary> | |
public static IConfiguredObjectVerifier<TObject> WithFieldEqualityComparer<TObject, TValue>(this IConfiguredObjectVerifier<TObject> objectVerifier, string name, IEqualityComparer<TValue> comparer) | |
{ | |
objectVerifier.WithFieldEqualityComparer<TValue>(name, (a, b) => comparer.Equals(a, b)); | |
return objectVerifier; | |
} | |
/// <summary> | |
/// Specifies the equality comparer to use when verifying the given property of the verified object (comparison priority: table field, object property, target value, default) | |
/// </summary> | |
public static IConfiguredObjectVerifier<TObject> WithPropertyEqualityComparer<TObject, TValue>(this IConfiguredObjectVerifier<TObject> objectVerifier, Expression<Func<TObject, TValue>> selector, IEqualityComparer<TValue> comparer) | |
{ | |
objectVerifier.WithPropertyEqualityComparer<TValue>(selector, (a, b) => comparer.Equals(a, b)); | |
return objectVerifier; | |
} | |
/// <summary> | |
/// Specifies the equality comparer to use when verifying properties of the given type (comparison priority: table field, object property, target value, default) | |
/// </summary> | |
public static IConfiguredObjectVerifier<TObject> WithTypeEqualityComparer<TObject, TValue>(this IConfiguredObjectVerifier<TObject> objectVerifier, IEqualityComparer<TValue> comparer) | |
{ | |
objectVerifier.WithTypeEqualityComparer<TValue>((a, b) => comparer.Equals(a, b)); | |
return objectVerifier; | |
} | |
/// <summary> | |
/// Returns a converter function for the given type. This method is the default converter provider. | |
/// </summary> | |
public static Func<string, object> GetDefaultConverter(Type type) | |
{ | |
if (type == typeof(string)) | |
return s => s; | |
if (type.IsGenericType && type.Name == "Nullable`1") | |
return val => | |
{ | |
if (string.IsNullOrEmpty(val)) return null; | |
else return GetDefaultConverter(type.GetGenericArguments()[0])(val); | |
}; | |
if (type.IsEnum) | |
return value => Enum.Parse(type, value); | |
return value => TypeDescriptor.GetConverter(type).ConvertFromString(value); | |
} | |
} | |
public static class ExpressionExtensions | |
{ | |
/// <summary> | |
/// Returns the property name from a property selector expression | |
/// </summary> | |
public static string GetPropertyName<TObject, TValue>(this Expression<Func<TObject, TValue>> property) | |
{ | |
var exp = (LambdaExpression)property; | |
if (exp.Body.NodeType == ExpressionType.Parameter) | |
return "item"; | |
var mExp = (exp.Body.NodeType == ExpressionType.MemberAccess) ? | |
(MemberExpression)exp.Body : | |
(MemberExpression)((UnaryExpression)exp.Body).Operand; | |
return mExp.Member.Name; | |
} | |
} | |
public interface IObjectVerifier<TObject> | |
{ | |
/// <summary> | |
/// Allows customizing the object verification | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithConfiguration(); | |
/// <summary> | |
/// Verifies the object with a vertical property-value collection | |
/// </summary> | |
string[] Verify(IEnumerable<IDictionary<string, string>> expected, TObject actual); | |
} | |
public interface IConfiguredObjectVerifier<TObject> : IObjectVerifier<TObject> | |
{ | |
/// <summary> | |
/// Specifies an alias in the verification data for the given property of the object. There can be more than one alias for any property, but not vice versa. | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithPropertyAlias<TProperty>(Expression<Func<TObject, TProperty>> selector, string alias); | |
/// <summary> | |
/// Specifies the converter to use for the given verification data field (converter priority: table field, object property, target value, default) | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithFieldValueConverter<TProperty>(string name, Func<string, TProperty> converter); | |
/// <summary> | |
/// Specifies the converter to use for the given property of the verified object (converter priority: table field, object property, target value, default) | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithPropertyValueConverter<TProperty>(Expression<Func<TObject, TProperty>> selector, Func<string, TProperty> converter); | |
/// <summary> | |
/// Specifies a conversion method to use when converting to the given value (converter priority: table field, object property, target value, default) | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithTypeValueConverter<TValue>(Func<string, TValue> converter); | |
/// <summary> | |
/// Specifies the provider method to use to obtain the default value converter for any given type (converter priority: table field, object property, target value, default) | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithDefaultConverter(Func<Type, Func<string, object>> converterProvider); | |
/// <summary> | |
/// Specifies the equality comparison method to use when verifying the given field mapped to the verified object (comparison priority: table field, object property, target value, default) | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithFieldEqualityComparer<TValue>(string name, Func<TValue, TValue, bool> comparer); | |
/// <summary> | |
/// Specifies the equality comparison method to use when verifying the given property of the verified object (comparison priority: table field, object property, target value, default) | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithPropertyEqualityComparer<TValue>(Expression<Func<TObject, TValue>> selector, Func<TValue, TValue, bool> comparer); | |
/// <summary> | |
/// Specifies the equality comparison method to use when verifying properties of the given type (comparison priority: table field, object property, target value, default) | |
/// </summary> | |
IConfiguredObjectVerifier<TObject> WithTypeEqualityComparer<TValue>(Func<TValue, TValue, bool> comparer); | |
} | |
public static class ObjectVerifier | |
{ | |
/// <summary> | |
/// Creates an object verifier for a vertical property-value collection with the specified field keys. | |
/// </summary> | |
/// <param name="keyField">field for property names</param> | |
/// <param name="valueField">field for property values</param> | |
public static IObjectVerifier<TObject> For<TObject>(string keyField, string valueField) | |
{ | |
return new ObjectVerifier<TObject>(keyField, valueField); | |
} | |
} | |
internal sealed class ObjectVerifier<TObject> : IConfiguredObjectVerifier<TObject> | |
{ | |
private readonly string keyField; | |
private readonly string valueField; | |
internal ObjectVerifier(string keyField, string valueField) | |
{ | |
this.keyField = keyField; | |
this.valueField = valueField; | |
} | |
public string[] Verify(IEnumerable<IDictionary<string, string>> expectedObject, TObject actualObject) | |
{ | |
var errors = new List<string>(); | |
foreach (var item in expectedObject) | |
{ | |
var fieldName = item[keyField]; | |
var propertyName = fieldName; | |
if (propertyAliases.ContainsKey(propertyName)) | |
propertyName = propertyAliases[propertyName]; | |
var property = typeof(TObject).GetProperty(propertyName); | |
if (property == null) | |
throw new InvalidOperationException(string.Format("Invalid property name '{0}'", propertyName)); | |
Func<string, object> converter = null; | |
if (converter == null) fieldValueConverters.TryGetValue(fieldName, out converter); | |
if (converter == null) propertyValueConverters.TryGetValue(propertyName, out converter); | |
if (converter == null) valueConverters.TryGetValue(property.PropertyType, out converter); | |
if (converter == null) converter = defaultConverterProvider(property.PropertyType); | |
Func<object, object, bool> comparer = null; | |
if (comparer == null) fieldComparers.TryGetValue(fieldName, out comparer); | |
if (comparer == null) propertyComparers.TryGetValue(propertyName, out comparer); | |
if (comparer == null) equalityComparers.TryGetValue(property.PropertyType, out comparer); | |
if (comparer == null) comparer = (exp, act) => object.Equals(exp, act); | |
var expectedValue = converter(item[valueField]); | |
var actualValue = property.GetValue(actualObject); | |
if (!comparer(expectedValue, actualValue)) | |
errors.Add(string.Format("The values for {0} do not match (expected:<{2}>, actual:<{3}>)", | |
fieldName, propertyName, expectedValue, actualValue)); | |
} | |
return errors.ToArray(); | |
} | |
public IConfiguredObjectVerifier<TObject> WithConfiguration() | |
{ | |
return this; | |
} | |
private Func<Type, Func<string, object>> defaultConverterProvider = ObjectVerifierExtensions.GetDefaultConverter; | |
public IConfiguredObjectVerifier<TObject> WithDefaultConverter(Func<Type, Func<string, object>> converterProvider) | |
{ | |
defaultConverterProvider = converterProvider; | |
return this; | |
} | |
private readonly Dictionary<string, string> propertyAliases = new Dictionary<string, string>(); | |
public IConfiguredObjectVerifier<TObject> WithPropertyAlias<TProperty>(Expression<Func<TObject, TProperty>> selector, string alias) | |
{ | |
propertyAliases.Add(alias, selector.GetPropertyName()); | |
return this; | |
} | |
private readonly Dictionary<string, Func<string, object>> fieldValueConverters = new Dictionary<string, Func<string, object>>(); | |
public IConfiguredObjectVerifier<TObject> WithFieldValueConverter<TProperty>(string name, Func<string, TProperty> converter) | |
{ | |
fieldValueConverters.Add(name, v => converter(v)); | |
return this; | |
} | |
private readonly Dictionary<string, Func<string, object>> propertyValueConverters = new Dictionary<string, Func<string, object>>(); | |
public IConfiguredObjectVerifier<TObject> WithPropertyValueConverter<TProperty>(Expression<Func<TObject, TProperty>> selector, Func<string, TProperty> converter) | |
{ | |
propertyValueConverters.Add(selector.GetPropertyName(), v => converter(v)); | |
return this; | |
} | |
private readonly Dictionary<Type, Func<string, object>> valueConverters = new Dictionary<Type, Func<string, object>>(); | |
public IConfiguredObjectVerifier<TObject> WithTypeValueConverter<TValue>(Func<string, TValue> converter) | |
{ | |
valueConverters.Add(typeof(TValue), v => converter(v)); | |
return this; | |
} | |
private readonly Dictionary<string, Func<object, object, bool>> fieldComparers = new Dictionary<string, Func<object, object, bool>>(); | |
public IConfiguredObjectVerifier<TObject> WithFieldEqualityComparer<TValue>(string name, Func<TValue, TValue, bool> comparer) | |
{ | |
fieldComparers.Add(name, (a, b) => comparer((TValue)a, (TValue)b)); | |
return this; | |
} | |
private readonly Dictionary<string, Func<object, object, bool>> propertyComparers = new Dictionary<string, Func<object, object, bool>>(); | |
public IConfiguredObjectVerifier<TObject> WithPropertyEqualityComparer<TValue>(Expression<Func<TObject, TValue>> selector, Func<TValue, TValue, bool> comparer) | |
{ | |
propertyComparers.Add(selector.GetPropertyName(), (a, b) => comparer((TValue)a, (TValue)b)); | |
return this; | |
} | |
private readonly Dictionary<Type, Func<object, object, bool>> equalityComparers = new Dictionary<Type, Func<object, object, bool>>(); | |
public IConfiguredObjectVerifier<TObject> WithTypeEqualityComparer<TValue>(Func<TValue, TValue, bool> comparer) | |
{ | |
equalityComparers.Add(typeof(TValue), (a, b) => comparer((TValue)a, (TValue)b)); | |
return this; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment