Collection size from attribute for Autofixture declarative autodata parameter
Asked Answered
Q

1

4

How to a specify a list/enumerable's length/size using an attribute on a property passed into a test using Autofixture's declarative parameter style?

I want to be able to make this test pass without moving parameters into the test body.

        [Theory, AutoData]
        public void CollectionSizeTest(
            List<int> defaultSize,
            List<int> customSize,
            List<int> customSize2,
            IEnumerable<string> empty
        )
        {
            Assert.Equal(3, defaultSize.Count);
            Assert.Equal(5, customSize.Count);
            Assert.Equal(6, customSize2.Count);
            Assert.Empty(empty);
        }
Quincentenary answered 26/5, 2021 at 22:13 Comment(0)
Q
5

You can create a custom attribute for this, such as this CollectionSizeAttribute:

        [Theory, AutoData]
        public void CollectionSizeTest(
            List<int> defaultSize,
            [CollectionSize(5)] List<int> customSize,
            [CollectionSize(6)] List<int> customSize2,
            [CollectionSize(0)] IEnumerable<string> empty,
            List<string> defaultSize2
        )
        {
            Assert.Equal(3, defaultSize.Count);
            Assert.Equal(5, customSize.Count);
            Assert.Equal(6, customSize2.Count);
            Assert.Empty(empty);
            Assert.Equal(3, defaultSize2.Count);
        }

        public class CollectionSizeAttribute : CustomizeAttribute
        {
            private readonly int _size;

            public CollectionSizeAttribute(int size)
            {
                _size = size;
            }

            public override ICustomization GetCustomization(ParameterInfo parameter)
            {
                if (parameter == null) throw new ArgumentNullException(nameof(parameter));

                var objectType = parameter.ParameterType.GetGenericArguments()[0];

                var isTypeCompatible =
                    parameter.ParameterType.IsGenericType
                    && parameter.ParameterType.GetGenericTypeDefinition().MakeGenericType(objectType).IsAssignableFrom(typeof(List<>).MakeGenericType(objectType))
                ;
                if (!isTypeCompatible)
                {
                    throw new InvalidOperationException($"{nameof(CollectionSizeAttribute)} specified for type incompatible with List: {parameter.ParameterType} {parameter.Name}");
                }

                var customizationType = typeof(CollectionSizeCustomization<>).MakeGenericType(objectType);
                return (ICustomization) Activator.CreateInstance(customizationType, parameter, _size);
            }

            public class CollectionSizeCustomization<T> : ICustomization
            {
                private readonly ParameterInfo _parameter;
                private readonly int _repeatCount;

                public CollectionSizeCustomization(ParameterInfo parameter, int repeatCount)
                {
                    _parameter = parameter;
                    _repeatCount = repeatCount;
                }

                public void Customize(IFixture fixture)
                {
                    fixture.Customizations.Add(new FilteringSpecimenBuilder(
                        new FixedBuilder(fixture.CreateMany<T>(_repeatCount).ToList()),
                        new EqualRequestSpecification(_parameter)
                    ));
                }
            }
        }

This causes the parameter to be created as a list with the given size by calling fixture.CreateMany<T>(_repeatCount).

Quincentenary answered 26/5, 2021 at 22:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.