LSTANCZYK
10/17/2014 - 2:53 PM

SpecFlow.CollectionVerifier.cs

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;
        }
    }
}