diff --git a/SpecFlow.Assist.Dynamic.Specs/PropertyNameFormatting.feature b/SpecFlow.Assist.Dynamic.Specs/PropertyNameFormatting.feature new file mode 100644 index 0000000..5613e28 --- /dev/null +++ b/SpecFlow.Assist.Dynamic.Specs/PropertyNameFormatting.feature @@ -0,0 +1,17 @@ +Feature: PropertyNameFormatting + As a developer + I want the property names of my dynamic code to be formatted as specified + So that I can perform comparisons + +Scenario: Property names are left untouched when preserving formatting + When I create a dynamic instance with preserved property names from this table + | This is my (Name) | age | + | Chris | 37 | + Then the 'This is my (Name)' property should equal 'Chris' + And the age property should equal 37 + +Scenario: Property names are formatted as expected when using a custom formatter + When I create a dynamic instance from this table using the 'CustomPropertyNameFormatter' property name formatter + | Customer ID | + | C123 | + Then the 'CustomerID' property should equal 'C123' diff --git a/SpecFlow.Assist.Dynamic.Specs/PropertyNameFormatting/CustomPropertyNameFormatter.cs b/SpecFlow.Assist.Dynamic.Specs/PropertyNameFormatting/CustomPropertyNameFormatter.cs new file mode 100644 index 0000000..c2b327c --- /dev/null +++ b/SpecFlow.Assist.Dynamic.Specs/PropertyNameFormatting/CustomPropertyNameFormatter.cs @@ -0,0 +1,9 @@ +using SpecFlow.Assist.Dynamic.PropertyNameFormatting; + +namespace SpecFlow.Assist.Dynamic.Specs.PropertyNameFormatting +{ + public class CustomPropertyNameFormatter : IPropertyNameFormatter + { + public string Format(string name) => name.Replace(" ", string.Empty); + } +} diff --git a/SpecFlow.Assist.Dynamic.Specs/Steps/DynamicInstanceCreationSteps.cs b/SpecFlow.Assist.Dynamic.Specs/Steps/DynamicInstanceCreationSteps.cs index 94e8f43..6abcc12 100644 --- a/SpecFlow.Assist.Dynamic.Specs/Steps/DynamicInstanceCreationSteps.cs +++ b/SpecFlow.Assist.Dynamic.Specs/Steps/DynamicInstanceCreationSteps.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using NUnit.Framework; +using SpecFlow.Assist.Dynamic.PropertyNameFormatting; using TechTalk.SpecFlow; using TechTalk.SpecFlow.Assist; @@ -20,6 +23,34 @@ public void CreateDynamicInstanceFromTable(Table table) this.state.OriginalInstance = table.CreateDynamicInstance(); } + [When(@"I create a dynamic instance with preserved property names from this table")] + public void WhenICreateADynamicInstanceWithPreservedPropertyNamesFromThisTable(Table table) + { + this.state.OriginalInstance = table.CreateDynamicInstance(new PreservePropertyNameFormatter()); + } + + [When(@"I create a dynamic instance from this table using the '(.*)' property name formatter")] + public void WhenICreateADynamicInstanceFromThisTableUsingThePropertyNameFormatter(string propertyNameFormatter, Table table) + { + var executingAssembly = Assembly.GetExecutingAssembly(); + var propertyNameFormatterType = executingAssembly + .GetTypes() + .SingleOrDefault(t => t.GetInterfaces().Contains(typeof(IPropertyNameFormatter)) && t.Name.EndsWith(propertyNameFormatter)); + + Assert.IsNotNull(propertyNameFormatterType, "No types implementing {0} found in assembly {1}", nameof(IPropertyNameFormatter), executingAssembly.FullName); + + var customPropertyNameFormatter = Activator.CreateInstance(propertyNameFormatterType) as IPropertyNameFormatter; + + this.state.OriginalInstance = table.CreateDynamicInstance(customPropertyNameFormatter); + } + + [Then(@"the '(.*)' property should equal '(.*)'")] + public void PropertyShouldBe(string propertyName, string expectedValue) + { + var actual = ((IDictionary)this.state.OriginalInstance)[propertyName]; + Assert.AreEqual(expectedValue, actual); + } + [Then(@"the Name property should equal '(.*)'")] public void NameShouldBe(string expectedValue) { diff --git a/SpecFlow.Assist.Dynamic/DynamicTableHelpers.cs b/SpecFlow.Assist.Dynamic/DynamicTableHelpers.cs index 96fa53e..1670e47 100644 --- a/SpecFlow.Assist.Dynamic/DynamicTableHelpers.cs +++ b/SpecFlow.Assist.Dynamic/DynamicTableHelpers.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Dynamic; using System.Linq; -using System.Text.RegularExpressions; using Dynamitey; -using Dynamitey.DynamicObjects; +using SpecFlow.Assist.Dynamic.PropertyNameFormatting; namespace TechTalk.SpecFlow.Assist { @@ -23,6 +22,8 @@ public static class DynamicTableHelpers private const string ERRORMESS_SET_VALUES_DIFFERS = "A difference was found on row '{0}' for column '{1}' (property '{2}').\n\tInstance:\t'{3}'(type: {4}).\n\tTable:\t\t'{5}'(type: {6})"; + private static readonly IPropertyNameFormatter defaultPropertyNameFormatter = new DefaultPropertyNameFormatter(); + /// /// Create a dynamic object from the headers and values of the /// @@ -31,20 +32,34 @@ public static class DynamicTableHelpers /// the created object public static ExpandoObject CreateDynamicInstance(this Table table, bool doTypeConversion = true) { - if (table.Header.Count == 2 && table.RowCount > 1) - { - var horizontalTable = CreateHorizontalTable(table); - return CreateDynamicInstance(horizontalTable.Rows[0], doTypeConversion); - } + return CreateDynamicInstance(table, defaultPropertyNameFormatter, doTypeConversion); + } - if (table.RowCount == 1) - { - return CreateDynamicInstance(table.Rows[0], doTypeConversion); - } + /// + /// Create a dynamic object from the headers and values of the + /// + /// the table to create a dynamic object from + /// The formatter to use to format property names + /// should types be converted according to conventions described in https://github.com/marcusoftnet/SpecFlow.Assist.Dynamic/wiki/Conversion-conventions#property-type-conversions + /// the created object + public static ExpandoObject CreateDynamicInstance( + this Table table, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) + { + if (table.Header.Count == 2 && table.RowCount > 1) + { + var horizontalTable = CreateHorizontalTable(table); + return CreateDynamicInstance(horizontalTable.Rows[0], propertyNameFormatter, doTypeConversion); + } - throw new DynamicInstanceFromTableException(ERRORMESS_INSTANCETABLE_FORMAT); - } + if (table.RowCount == 1) + { + return CreateDynamicInstance(table.Rows[0], propertyNameFormatter, doTypeConversion); + } + throw new DynamicInstanceFromTableException(ERRORMESS_INSTANCETABLE_FORMAT); + } /// /// Creates a set of dynamic objects based of the headers and values @@ -54,8 +69,23 @@ public static ExpandoObject CreateDynamicInstance(this Table table, bool doTypeC /// a set of dynamics public static IEnumerable CreateDynamicSet(this Table table, bool doTypeConversion = true) { - return from r in table.Rows - select CreateDynamicInstance(r, doTypeConversion); + return CreateDynamicSet(table, defaultPropertyNameFormatter, doTypeConversion); + } + + /// + /// Creates a set of dynamic objects based of the headers and values + /// + /// the table to create a set of dynamics from + /// The formatter to use to format property names + /// should types be converted according to conventions described in https://github.com/marcusoftnet/SpecFlow.Assist.Dynamic/wiki/Conversion-conventions#property-type-conversions + /// a set of dynamics + public static IEnumerable CreateDynamicSet( + this Table table, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) + { + return from r in table.Rows + select CreateDynamicInstance(r, propertyNameFormatter, doTypeConversion); } /// @@ -67,11 +97,28 @@ public static IEnumerable CreateDynamicSet(this Table table, bool doTyp /// should types be converted according to conventions described in https://github.com/marcusoftnet/SpecFlow.Assist.Dynamic/wiki/Conversion-conventions#property-type-conversions public static void CompareToDynamicInstance(this Table table, dynamic instance, bool doTypeConversion = true) { - IList propDiffs = GetPropertyDifferences(table, instance); - if (propDiffs.Any()) - throw new DynamicInstanceComparisonException(propDiffs); + CompareToDynamicInstance(table, instance, defaultPropertyNameFormatter, doTypeConversion); + } - AssertValuesOfRowDifference(table.Rows[0], instance, doTypeConversion); + /// + /// Validates if a dynamic instance matches the + /// Throws descriptive exception if not + /// + /// the table to compare the instance against + /// the instance to compare the table against + /// The formatter to use to format property names + /// should types be converted according to conventions described in https://github.com/marcusoftnet/SpecFlow.Assist.Dynamic/wiki/Conversion-conventions#property-type-conversions + public static void CompareToDynamicInstance( + this Table table, + dynamic instance, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) + { + IList propDiffs = GetPropertyDifferences(table, instance, propertyNameFormatter); + if (propDiffs.Any()) + throw new DynamicInstanceComparisonException(propDiffs); + + AssertValuesOfRowDifference(table.Rows[0], instance, propertyNameFormatter, doTypeConversion); } /// @@ -83,25 +130,46 @@ public static void CompareToDynamicInstance(this Table table, dynamic instance, /// should types be converted according to conventions described in https://github.com/marcusoftnet/SpecFlow.Assist.Dynamic/wiki/Conversion-conventions#property-type-conversions public static void CompareToDynamicSet(this Table table, IList set, bool doTypeConversion = true) { - AssertEqualNumberOfRows(table, set); + CompareToDynamicSet(table, set, defaultPropertyNameFormatter, doTypeConversion); + } - IList propDiffs = GetPropertyDifferences(table, set[0]); - if (propDiffs.Any()) - { - throw new DynamicSetComparisonException(ERRORMESS_PROPERTY_DIFF_SET, propDiffs); - } + /// + /// Validates that the dynamic set matches the + /// Throws descriptive exception if not + /// + /// the table to compare the set against + /// the set to compare the table against + /// The formatter to use to format property names + /// should types be converted according to conventions described in https://github.com/marcusoftnet/SpecFlow.Assist.Dynamic/wiki/Conversion-conventions#property-type-conversions + public static void CompareToDynamicSet( + this Table table, + IList set, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) + { + AssertEqualNumberOfRows(table, set); - // Now we know that the table and the list has the same number of rows and properties + IList propDiffs = GetPropertyDifferences(table, set[0], propertyNameFormatter); + if (propDiffs.Any()) + { + throw new DynamicSetComparisonException(ERRORMESS_PROPERTY_DIFF_SET, propDiffs); + } - var valueDifference = GetSetValueDifferences(table, set, doTypeConversion); + // Now we know that the table and the list has the same number of rows and properties - if (valueDifference.Any()) - { - throw new DynamicSetComparisonException(ERRORMESS_PROPERTY_DIFF_SET, valueDifference); - } + var valueDifference = GetSetValueDifferences(table, set, propertyNameFormatter, doTypeConversion); + + if (valueDifference.Any()) + { + throw new DynamicSetComparisonException(ERRORMESS_PROPERTY_DIFF_SET, valueDifference); + } } - private static List GetSetValueDifferences(Table table, IList set, bool doTypeConversion = true) + private static List GetSetValueDifferences( + Table table, + IList set, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) { var memberNames = Dynamic.GetMemberNames(set[0]); var valueDifference = new List(); @@ -111,7 +179,7 @@ private static List GetSetValueDifferences(Table table, IList se foreach (var memberName in memberNames) { var currentHeader = string.Empty; - var rowValue = GetRowValue(i, table, memberName, out currentHeader, doTypeConversion); + var rowValue = GetRowValue(i, table, memberName, propertyNameFormatter, out currentHeader, doTypeConversion); var rowType = rowValue.GetType().Name; var instanceValue = Dynamic.InvokeGet(set[i], memberName); var instanceType = instanceValue.GetType().Name; @@ -134,13 +202,19 @@ private static List GetSetValueDifferences(Table table, IList se return valueDifference; } - private static object GetRowValue(int rowIndex, Table table, string memberName, out string currentHeader, bool doTypeConversion = true) + private static object GetRowValue( + int rowIndex, + Table table, + string memberName, + IPropertyNameFormatter propertyNameFormatter, + out string currentHeader, + bool doTypeConversion = true) { object rowValue = null; currentHeader = string.Empty; foreach (var header in table.Header) { - if (CreatePropertyName(header) == memberName) + if (propertyNameFormatter.Format(header) == memberName) { currentHeader = header; rowValue = CreateTypedValue(table.Rows[rowIndex][header], doTypeConversion); @@ -150,16 +224,23 @@ private static object GetRowValue(int rowIndex, Table table, string memberName, return rowValue; } - private static void AssertValuesOfRowDifference(TableRow tableRow, dynamic instance, bool doTypeConversion = true) + private static void AssertValuesOfRowDifference( + TableRow tableRow, + dynamic instance, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) { - IList valueDiffs = ValidateValuesOfRow(tableRow, instance, doTypeConversion); + IList valueDiffs = ValidateValuesOfRow(tableRow, instance, propertyNameFormatter, doTypeConversion); if (valueDiffs.Any()) throw new DynamicInstanceComparisonException(valueDiffs); } - private static IList GetPropertyDifferences(Table table, dynamic instance, bool doTypeConversion = true) + private static IList GetPropertyDifferences( + Table table, + dynamic instance, + IPropertyNameFormatter propertyNameFormatter) { - var tableHeadersAsPropertyNames = table.Header.Select(CreatePropertyName); + var tableHeadersAsPropertyNames = table.Header.Select(propertyNameFormatter.Format); IEnumerable instanceMembers = Dynamic.GetMemberNames(instance); return GetPropertyNameDifferences(tableHeadersAsPropertyNames, instanceMembers); @@ -174,13 +255,17 @@ private static void AssertEqualNumberOfRows(Table table, IList set) } } - private static IList ValidateValuesOfRow(TableRow tableRow, dynamic instance, bool doTypeConversion = true) + private static IList ValidateValuesOfRow( + TableRow tableRow, + dynamic instance, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) { var valueDiffs = new List(); foreach (var header in tableRow.Keys) { - var propertyName = CreatePropertyName(header); + var propertyName = propertyNameFormatter.Format(header); var valueFromInstance = Dynamic.InvokeGet(instance, propertyName); var typeFromInstance = valueFromInstance.GetType().Name; var valueFromTable = CreateTypedValue(tableRow[header], doTypeConversion); @@ -223,14 +308,17 @@ private static Table CreateHorizontalTable(Table verticalFieldValueTable) return horizontalTable; } - private static ExpandoObject CreateDynamicInstance(TableRow tablerow, bool doTypeConversion = true) + private static ExpandoObject CreateDynamicInstance( + TableRow tablerow, + IPropertyNameFormatter propertyNameFormatter, + bool doTypeConversion = true) { dynamic expando = new ExpandoObject(); var dicExpando = expando as IDictionary; foreach (var header in tablerow.Keys) { - var propName = CreatePropertyName(header); + var propName = propertyNameFormatter.Format(header); var propValue = CreateTypedValue(tablerow[header], doTypeConversion); dicExpando.Add(propName, propValue); } @@ -268,42 +356,5 @@ private static object CreateTypedValue(string valueFromTable, bool doTypeConvers return valueFromTable; } - - private static string CreatePropertyName(string header) - { - var cleanedHeader = RemoveReservedChars(header); - var propName = FixCasing(cleanedHeader); - - // Throw if no chars in string - if (propName.Length != 0) return propName; - - var mess = string.Format("Property '{0}' only contains reserved C# characters", header); - throw new DynamicInstanceFromTableException(mess); - } - - private static string FixCasing(string header) - { - var arr = header.Split(' '); - var propName = arr[0]; // leave the first element as is, since it might be correct cased... - - for (var i = 1; i < arr.Length; i++) - { - var s = arr[i]; - if (s.Length > 0) - { - propName += s[0].ToString().ToUpperInvariant() + - s.Substring(1).ToLowerInvariant(); - } - } - - return propName; - } - - private static string RemoveReservedChars(string orgPropertyName) - { - const string pattern = @"[^\w\s]"; - const string replacement = ""; - return Regex.Replace(orgPropertyName, pattern, replacement); - } } } diff --git a/SpecFlow.Assist.Dynamic/PropertyNameFormatting/DefaultPropertyNameFormatter.cs b/SpecFlow.Assist.Dynamic/PropertyNameFormatting/DefaultPropertyNameFormatter.cs new file mode 100644 index 0000000..63dc9ca --- /dev/null +++ b/SpecFlow.Assist.Dynamic/PropertyNameFormatting/DefaultPropertyNameFormatter.cs @@ -0,0 +1,47 @@ +using System.Text.RegularExpressions; +using TechTalk.SpecFlow.Assist; + +namespace SpecFlow.Assist.Dynamic.PropertyNameFormatting +{ + public sealed class DefaultPropertyNameFormatter : IPropertyNameFormatter + { + public string Format(string name) + { + var cleanedHeader = RemoveReservedChars(name); + var propName = FixCasing(cleanedHeader); + + // Throw if no chars in string + if (propName.Length != 0) return propName; + + var mess = $"Property '{name}' only contains reserved C# characters"; + throw new DynamicInstanceFromTableException(mess); + } + + private static string FixCasing(string header) + { + var arr = header.Split(' '); + + // leave the first element as is, since it might be correctly cased... + var propName = arr[0]; + + for (var i = 1; i < arr.Length; i++) + { + var s = arr[i]; + if (s.Length > 0) + { + propName += s[0].ToString().ToUpperInvariant() + + s.Substring(1).ToLowerInvariant(); + } + } + + return propName; + } + + private static string RemoveReservedChars(string orgPropertyName) + { + const string pattern = @"[^\w\s]"; + const string replacement = ""; + return Regex.Replace(orgPropertyName, pattern, replacement); + } + } +} diff --git a/SpecFlow.Assist.Dynamic/PropertyNameFormatting/IPropertyNameFormatter.cs b/SpecFlow.Assist.Dynamic/PropertyNameFormatting/IPropertyNameFormatter.cs new file mode 100644 index 0000000..83895e6 --- /dev/null +++ b/SpecFlow.Assist.Dynamic/PropertyNameFormatting/IPropertyNameFormatter.cs @@ -0,0 +1,7 @@ +namespace SpecFlow.Assist.Dynamic.PropertyNameFormatting +{ + public interface IPropertyNameFormatter + { + string Format(string name); + } +} diff --git a/SpecFlow.Assist.Dynamic/PropertyNameFormatting/PreservePropertyNameFormatter.cs b/SpecFlow.Assist.Dynamic/PropertyNameFormatting/PreservePropertyNameFormatter.cs new file mode 100644 index 0000000..28809f1 --- /dev/null +++ b/SpecFlow.Assist.Dynamic/PropertyNameFormatting/PreservePropertyNameFormatter.cs @@ -0,0 +1,7 @@ +namespace SpecFlow.Assist.Dynamic.PropertyNameFormatting +{ + public sealed class PreservePropertyNameFormatter : IPropertyNameFormatter + { + public string Format(string name) => name; + } +}