Skip to content

Instantly share code, notes, and snippets.

@quexy
Last active August 29, 2015 14:01
Show Gist options
  • Save quexy/66dff399be40bdbe7057 to your computer and use it in GitHub Desktop.
Save quexy/66dff399be40bdbe7057 to your computer and use it in GitHub Desktop.
Collection verifier for SpecFlow
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace TechTalk.SpecFlow.ObjectVerification
{
public static class CollectionVerifierExtensions
{
/// <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 IConfiguredCollectionVerifier<TObject> WithPropertyAlias<TObject, TProperty>(this IConfiguredCollectionVerifier<TObject> collectionVerifier, Expression<Func<TObject, TProperty>> selector, params string[] aliases)
{
foreach (var alias in aliases)
collectionVerifier.WithPropertyAlias(selector, alias);
return collectionVerifier;
}
/// <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 IConfiguredCollectionVerifier<TObject> WithFieldEqualityComparer<TObject, TValue>(this IConfiguredCollectionVerifier<TObject> collectionVerifier, string name, IEqualityComparer<TValue> comparer)
{
collectionVerifier.WithFieldEqualityComparer<TValue>(name, (a, b) => comparer.Equals(a, b));
return collectionVerifier;
}
/// <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 IConfiguredCollectionVerifier<TObject> WithPropertyEqualityComparer<TObject, TValue>(this IConfiguredCollectionVerifier<TObject> collectionVerifier, Expression<Func<TObject, TValue>> selector, IEqualityComparer<TValue> comparer)
{
collectionVerifier.WithPropertyEqualityComparer<TValue>(selector, (a, b) => comparer.Equals(a, b));
return collectionVerifier;
}
/// <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 IConfiguredCollectionVerifier<TObject> WithTypeEqualityComparer<TObject, TValue>(this IConfiguredCollectionVerifier<TObject> collectionVerifier, IEqualityComparer<TValue> comparer)
{
collectionVerifier.WithTypeEqualityComparer<TValue>((a, b) => comparer.Equals(a, b));
return collectionVerifier;
}
}
public interface ICollectionVerifier<TObject>
{
/// <summary>
/// Allows customizing the collection object verification
/// </summary>
IConfiguredCollectionVerifier<TObject> WithConfiguration();
/// <summary>
/// Verifies whether the two collections are equal, that is their object counts are the same, and the object at each position are matching
/// </summary>
string[] AreEqual(IEnumerable<IDictionary<string, string>> expectedCollection, IEnumerable<TObject> actualCollection);
/// <summary>
/// Verifies whether the two collections are equivalent, that is their object counts are the same, and each object has a pair regardless of their positions
/// </summary>
string[] AreEquivalent(IEnumerable<IDictionary<string, string>> expectedCollection, IEnumerable<TObject> actualCollection);
/// <summary>
/// Verifies whether every element in the subcollection has a pair in the main collection
/// </summary>
string[] IsSubset(IEnumerable<IDictionary<string, string>> subcollection, IEnumerable<TObject> mainCollection);
/// <summary>
/// Verifies whether every element in the subcollection has a pair in the main collection
/// </summary>
string[] IsSubset(IEnumerable<TObject> subcollection, IEnumerable<IDictionary<string, string>> mainCollection);
}
public interface IConfiguredCollectionVerifier<TObject> : ICollectionVerifier<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>
IConfiguredCollectionVerifier<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>
IConfiguredCollectionVerifier<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>
IConfiguredCollectionVerifier<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>
IConfiguredCollectionVerifier<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>
IConfiguredCollectionVerifier<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>
IConfiguredCollectionVerifier<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>
IConfiguredCollectionVerifier<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>
IConfiguredCollectionVerifier<TObject> WithTypeEqualityComparer<TValue>(Func<TValue, TValue, bool> comparer);
}
public static class CollectionVerifier
{
/// <summary>
/// Creates a collection verifier for collections of the given type
/// </summary>
public static ICollectionVerifier<TObject> For<TObject>()
{
return new CollectionVerifier<TObject>();
}
}
internal sealed class CollectionVerifier<TObject> : IConfiguredCollectionVerifier<TObject>
{
private static string keyField = "key";
private static string valueField = "value";
private readonly IObjectVerifier<TObject> objectVerifier;
public CollectionVerifier()
{
objectVerifier = ObjectVerifier.For<TObject>(keyField, valueField);
}
private IEnumerable<IDictionary<string, string>> ConvertExpectedObject(IDictionary<string, string> expectedObject)
{
return expectedObject.Select(e => new Dictionary<string, string> { { keyField, e.Key }, { valueField, e.Value } });
}
public string[] AreEqual(IEnumerable<IDictionary<string, string>> expectedCollection, IEnumerable<TObject> actualCollection)
{
var expectedArray = expectedCollection.ToArray();
var actualArray = actualCollection.ToArray();
var expectedCount = expectedArray.Length;
var actualCount = actualArray.Length;
if (expectedCount != actualCount)
return new[] { string.Format("The collection sizes are not equal (expected count: <{0}>; actual count: <{1}>)", expectedCount, actualCount) };
var errors = new List<string>();
for (int index = 0; index < expectedCount; ++index)
{
var verificationResult = objectVerifier.Verify(ConvertExpectedObject(expectedArray[index]), actualArray[index]);
if (verificationResult.Length != 0)
errors.Add(string.Format("Objects at index {0} do not match: {1}", index, string.Join(",", verificationResult)));
}
return errors.ToArray();
}
public string[] AreEquivalent(IEnumerable<IDictionary<string, string>> expectedCollection, IEnumerable<TObject> actualCollection)
{
var expectedArray = expectedCollection.ToArray();
var actualArray = actualCollection.ToArray();
var expectedCount = expectedArray.Length;
var actualCount = actualArray.Length;
if (expectedCount != actualCount)
return new[] { string.Format("The collection sizes are not equal (expected count: <{0}>; actual count: <{1}>)", expectedCount, actualCount) };
var errors = new List<string>();
foreach (var expectedObject in expectedArray)
{
var expected = ConvertExpectedObject(expectedObject);
if (!actualArray.Any(actual => objectVerifier.Verify(expected, actual).Length == 0))
errors.Add(string.Format("No matching object for {0}{2}{1}", "{", "}",
string.Join(",", expectedObject.Select(e => string.Format("{0}:\"{1}\"", e.Key, e.Value)))));
}
return errors.ToArray();
}
public string[] IsSubset(IEnumerable<IDictionary<string, string>> subcollection, IEnumerable<TObject> mainCollection)
{
var expectedArray = subcollection.ToArray();
var actualArray = mainCollection.ToArray();
var expectedCount = expectedArray.Length;
var actualCount = actualArray.Length;
if (expectedCount > actualCount)
return new[] { string.Format("The subcollection is bigger than the main collection (expected count: <{0}>; actual count: <{1}>)", expectedCount, actualCount) };
var errors = new List<string>();
foreach (var expectedObject in expectedArray)
{
var expected = ConvertExpectedObject(expectedObject);
if (!actualArray.Any(actual => objectVerifier.Verify(expected, actual).Length == 0))
errors.Add(string.Format("No matching object for {0}{2}{1}", "{", "}",
string.Join(",", expectedObject.Select(e => string.Format("{0}:\"{1}\"", e.Key, e.Value)))));
}
return errors.ToArray();
}
public string[] IsSubset(IEnumerable<TObject> subcollection, IEnumerable<IDictionary<string, string>> mainCollection)
{
var expectedArray = subcollection.ToArray();
var actualArray = mainCollection.Select(ConvertExpectedObject).ToArray();
var expectedCount = expectedArray.Length;
var actualCount = actualArray.Length;
if (expectedCount > actualCount)
return new[] { string.Format("The subcollection is bigger than the main collection (expected count: <{0}>; actual count: <{1}>)", expectedCount, actualCount) };
var errors = new List<string>();
foreach (var expected in expectedArray)
{
if (!actualArray.Any(actual => objectVerifier.Verify(actual, expected).Length == 0))
errors.Add(string.Format("No matching object for {0}{2}{1}", "{", "}",
string.Join(",", typeof(TObject).GetProperties().Select(p => string.Format("{0}:\"{1}\"", p.Name, p.GetValue(expected))))));
}
return errors.ToArray();
}
public IConfiguredCollectionVerifier<TObject> WithConfiguration()
{
return this;
}
public IConfiguredCollectionVerifier<TObject> WithPropertyAlias<TProperty>(Expression<Func<TObject, TProperty>> selector, string alias)
{
objectVerifier.WithConfiguration().WithPropertyAlias(selector, alias);
return this;
}
public IConfiguredCollectionVerifier<TObject> WithFieldValueConverter<TProperty>(string name, Func<string, TProperty> converter)
{
objectVerifier.WithConfiguration().WithFieldValueConverter(name, converter);
return this;
}
public IConfiguredCollectionVerifier<TObject> WithPropertyValueConverter<TProperty>(Expression<Func<TObject, TProperty>> selector, Func<string, TProperty> converter)
{
objectVerifier.WithConfiguration().WithPropertyValueConverter(selector, converter);
return this;
}
public IConfiguredCollectionVerifier<TObject> WithTypeValueConverter<TValue>(Func<string, TValue> converter)
{
objectVerifier.WithConfiguration().WithTypeValueConverter(converter);
return this;
}
public IConfiguredCollectionVerifier<TObject> WithDefaultConverter(Func<Type, Func<string, object>> converterProvider)
{
objectVerifier.WithConfiguration().WithDefaultConverter(converterProvider);
return this;
}
public IConfiguredCollectionVerifier<TObject> WithFieldEqualityComparer<TValue>(string name, Func<TValue, TValue, bool> comparer)
{
objectVerifier.WithConfiguration().WithFieldEqualityComparer(name, comparer);
return this;
}
public IConfiguredCollectionVerifier<TObject> WithPropertyEqualityComparer<TValue>(Expression<Func<TObject, TValue>> selector, Func<TValue, TValue, bool> comparer)
{
objectVerifier.WithConfiguration().WithPropertyEqualityComparer(selector, comparer);
return this;
}
public IConfiguredCollectionVerifier<TObject> WithTypeEqualityComparer<TValue>(Func<TValue, TValue, bool> comparer)
{
objectVerifier.WithConfiguration().WithTypeEqualityComparer(comparer);
return this;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment