From 63a6f5c17ec45f515baaab00742d62a4f47317e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Zaj=C4=85c?= Date: Tue, 5 Dec 2023 14:12:38 +0100 Subject: [PATCH] Introduce attributes to refine customizations (#71) Introduce customizations to narrow generated values * [PickFromRange(-12, 7)] - only values from specified range should be generated, * [PickFromValues(-1, 3, 6, 9)] - values should only be selected from those specified, * [Except(5, 7)] - values from outside the specified list should be generated. --- README.md | 63 +++- src/.editorconfig | 9 +- .../Attributes/AutoMockDataAttributeTests.cs | 4 +- .../InlineAutoMockDataAttributeTests.cs | 4 +- .../MemberAutoMockDataAttributeTests.cs | 12 +- .../Attributes/AutoMockDataAttributeTests.cs | 4 +- .../InlineAutoMockDataAttributeTests.cs | 4 +- .../MemberAutoMockDataAttributeTests.cs | 12 +- .../Attributes/AutoMockDataAttributeTests.cs | 4 +- .../InlineAutoMockDataAttributeTests.cs | 4 +- .../MemberAutoMockDataAttributeTests.cs | 12 +- .../.editorconfig | 2 + .../AutoDataAdapterAttributeTests.cs | 6 +- .../Attributes/AutoDataBaseAttributeTests.cs | 6 +- .../Attributes/CustomizeWithAttributeTests.cs | 20 +- .../Attributes/ExceptAttributeTests.cs | 187 +++++++++++ .../IgnoreVirtualMembersAttributeTests.cs | 2 +- .../InlineAutoDataBaseAttributeTests.cs | 10 +- .../MemberAutoDataBaseAttributeTests.cs | 4 +- .../Attributes/PickFromRangeAttributeTests.cs | 306 +++++++++++++++++ .../PickFromValuesAttributeTests.cs | 317 ++++++++++++++++++ .../Common/EnumerableExtensionsTests.cs | 109 ++++++ .../Common/RoundRobinEnumerableTests.cs | 57 ++++ .../CustomizeAttributeComparerTests.cs | 2 +- .../AutoDataCommonCustomizationTests.cs | 4 +- ...DoNotThrowOnRecursionCustomizationTests.cs | 2 +- .../IgnoreVirtualMembersCustomizationTests.cs | 6 +- .../OmitOnRecursionCustomizationTests.cs | 2 +- .../MemberAutoDataItemConverterTests.cs | 2 +- .../AutoDataAttributeProviderTests.cs | 4 +- .../InlineAutoDataAttributeProviderTests.cs | 4 +- .../Requests/ValuesRequestTests.cs | 216 ++++++++++++ ...gnoreVirtualMembersSpecimenBuilderTests.cs | 12 +- .../RandomExceptValuesGeneratorTests.cs | 74 ++++ .../RandomFixedValuesGeneratorTests.cs | 116 +++++++ .../RequestFactoryRelayTests.cs | 199 +++++++++++ .../Attributes/CustomizeWithAttribute.cs | 3 +- .../Attributes/CustomizeWithAttribute[T].cs | 2 +- .../Attributes/ExceptAttribute.cs | 45 +++ .../Attributes/PickFromRangeAttribute.cs | 68 ++++ .../Attributes/PickFromValuesAttribute.cs | 45 +++ .../Common/EnumerableExtensions.cs | 62 ++++ .../Common/RoundRobinEnumerable[T].cs | 38 +++ .../Requests/ExceptValuesRequest.cs | 12 + .../Requests/FixedValuesRequest.cs | 12 + .../Requests/ValuesRequest.cs | 75 +++++ .../RandomExceptValuesGenerator.cs | 47 +++ .../RandomFixedValuesGenerator.cs | 50 +++ .../SpecimenBuilders/RequestFactoryRelay.cs | 65 ++++ 49 files changed, 2246 insertions(+), 79 deletions(-) create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/ExceptAttributeTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromRangeAttributeTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromValuesAttributeTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/EnumerableExtensionsTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/RoundRobinEnumerableTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/Requests/ValuesRequestTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomExceptValuesGeneratorTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomFixedValuesGeneratorTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RequestFactoryRelayTests.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Attributes/ExceptAttribute.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromRangeAttribute.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromValuesAttribute.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Common/EnumerableExtensions.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Common/RoundRobinEnumerable[T].cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Requests/ExceptValuesRequest.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Requests/FixedValuesRequest.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/Requests/ValuesRequest.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomExceptValuesGenerator.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomFixedValuesGenerator.cs create mode 100644 src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RequestFactoryRelay.cs diff --git a/README.md b/README.md index 5d6ec93b..55f0f672 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ An attribute that can be applied to parameters in an `AutoDataAttribute`-driven This attribute allows to disable the generation of members marked as `virtual` on a decorated type wheres `IgnoreVirtualMembers` arguments of mocking attributes mentioned above disable such a generation for all types created by `IFixture`. -**Caution:** Order is important! Applying `IgnoreVirtualMembers` attribute to the subsequent paramater makes precedig parameters of the same type to have `virtual` properties populated and the particular parameter with the following ones of the same type to have `virtual` properties unpopulated. +**Caution:** Order is important! Applying `IgnoreVirtualMembers` attribute to the subsequent parameter makes preceding parameters of the same type to have `virtual` properties populated and the particular parameter with the following ones of the same type to have `virtual` properties unpopulated. #### Example @@ -192,7 +192,7 @@ An attribute that can be applied to parameters in an `AutoDataAttribute`-driven - IncludeParameterType - indicates whether attribute target parameter `Type` should included as a first argument when creating customization; by default set to `false` -**Caution:** Order is important! Applying `CustomizeWith` attribute to the subsequent paramater makes precedig parameters of the same type to be created without specified customization and the particular parameter with the specified customization. +**Caution:** Order is important! Applying `CustomizeWith` attribute to the subsequent parameter makes preceding parameters of the same type to be created without specified customization and the particular parameter with the specified customization. #### Example @@ -285,6 +285,65 @@ public void CustomizeWithAttributeUsage( *** +## Data filtering attributes + +The following attributes helps narrowing down data generation to specific values or omitting certain values. + +For these attributes to work, they must be used in conjunction with other data generation attributes. + +They can be applied to simple types and collections. + +### Except + +An attribute ensuring that values from outside the specified list will be generated. + +#### Example + +```csharp +[Theory] +[AutoData] +public void ExceptAttributeUsage( + [Except(DayOfWeek.Saturday, DayOfWeek.Sunday)] DayOfWeek workday) +{ + Assert.True(workday is >= DayOfWeek.Monday and <= DayOfWeek.Friday); +} +``` + +### PickFromRange + +An attribute ensuring that only values from specified range will be generated. + +#### Example + +```csharp +[Theory] +[AutoData] +public void RangeAttributeUsage( + [PickFromRange(11, 19)] int teenagerAge) +{ + Assert.True(teenagerAge is > 11 and < 19); +} +``` + +### PickFromValues + +An attribute ensuring that only values from the specified list will be generated. + +#### Example + +```csharp +[Theory] +[AutoData] +public void ValuesAttributeUsage( + [PickFromValues(DayOfWeek.Saturday, DayOfWeek.Sunday)] HashSet weekend) +{ + var weekendDays = new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }; + Assert.Equivalent(weekendDays, weekend); +} +``` + +*** + ## Tips and tricks ### Fixture injection diff --git a/src/.editorconfig b/src/.editorconfig index fb8139f9..42c40c88 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -183,7 +183,7 @@ roslynator_equals_token_new_line = after roslynator_infinite_loop_style = while roslynator_max_line_length = 500 roslynator_new_line_at_end_of_file = true -roslynator_new_line_before_while_in_do_statement = false +roslynator_new_line_before_while_in_do_statement = true roslynator_null_conditional_operator_new_line = after roslynator_null_check_style = pattern_matching roslynator_object_creation_parentheses_style = include @@ -1368,7 +1368,7 @@ dotnet_diagnostic.RCS1235.severity = warning dotnet_diagnostic.RCS1236.severity = warning # RCS1237: Use bit shift operator. -dotnet_diagnostic.RCS1237.severity = suggestion +dotnet_diagnostic.RCS1237.severity = none # RCS1238: Avoid nested ?: operators. dotnet_diagnostic.RCS1238.severity = suggestion @@ -2176,6 +2176,9 @@ dotnet_diagnostic.CA1849.severity = warning # CA1850: Prefer static 'HashData' method over 'ComputeHash' dotnet_diagnostic.CA1850.severity = warning +# CA1859: Use concrete types when possible for improved performance +dotnet_diagnostic.CA1859.severity = suggestion + # CA2000: Dispose objects before losing scope dotnet_diagnostic.CA2000.severity = warning dotnet_code_quality.CA2000.excluded_symbol_names = @@ -3284,7 +3287,7 @@ dotnet_diagnostic.SA1519.severity = error dotnet_diagnostic.SA1520.severity = error dotnet_diagnostic.SA1600.severity = none dotnet_diagnostic.SA1601.severity = none -dotnet_diagnostic.SA1602.severity = error +dotnet_diagnostic.SA1602.severity = none dotnet_diagnostic.SA1603.severity = error dotnet_diagnostic.SA1604.severity = error dotnet_diagnostic.SA1605.severity = error diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/AutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/AutoMockDataAttributeTests.cs index d536db9c..f1d24bfa 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/AutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/AutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit.Sdk; [Collection("AutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class AutoMockDataAttributeTests { [Fact(DisplayName = "WHEN parameterless constructor is invoked THEN fixture and attribute provider are created")] @@ -34,9 +34,9 @@ public void WhenParameterlessConstructorIsInvoked_ThenFixtureAndAttributeProvide attribute.IgnoreVirtualMembers.Should().BeFalse(); } - [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] public void WhenGetDataIsInvoked_ThenFixtureIsConfiguredAndDataReturned(bool ignoreVirtualMembers) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/InlineAutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/InlineAutoMockDataAttributeTests.cs index 03f96acb..ace7cb48 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/InlineAutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/InlineAutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit.Sdk; [Collection("InlineAutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class InlineAutoMockDataAttributeTests { [Fact(DisplayName = "WHEN parameterless constructor is invoked THEN has no values but fixture and attribute provider are created")] @@ -67,9 +67,9 @@ public void GivenUninitializedValues_WhenConstructorIsInvoked_ThenHasNoValuesAnd attribute.Values.Should().HaveCount(0); } - [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] public void WhenGetDataIsInvoked_ThenFixtureIsConfiguredAndDataReturned(bool ignoreVirtualMembers) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/MemberAutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/MemberAutoMockDataAttributeTests.cs index c5414ffa..ab3a47f8 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/MemberAutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Tests/Attributes/MemberAutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit; [Collection("MemberAutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class MemberAutoMockDataAttributeTests { public static IEnumerable TestData { get; } = new[] @@ -42,8 +42,8 @@ public int TestMethod(int first, int second, int third, int fourth, IDisposable return first + second + third + fourth; } - [Theory(DisplayName = "WHEN constructor is invoked THEN has shared fixture")] [AutoData] + [Theory(DisplayName = "WHEN constructor is invoked THEN has shared fixture")] public void WhenConstructorIsInvoked_ThenHasSharedFixture(Fixture fixture) { // Arrange @@ -80,9 +80,9 @@ public void GivenExistingMemberName_WhenGetDataIsInvoked_ThenAppropriateDataIsRe } } - [Theory(DisplayName = "GIVEN IgnoreVirtualMembers WHEN GetData is invoked THEN fixture is customized correctly")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "GIVEN IgnoreVirtualMembers WHEN GetData is invoked THEN fixture is customized correctly")] public void GivenIgnoreVirtualMembers_WhenGetDataIsInvoked_ThenFixtureIsCustomizedCorrectly(bool ignoreVirtualMembers) { // Arrange @@ -111,8 +111,8 @@ public void GivenIgnoreVirtualMembers_WhenGetDataIsInvoked_ThenFixtureIsCustomiz customizations[1].Should().BeOfType(); } - [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to true THEN same fixture per data row is used.")] [MemberAutoMockData(nameof(TestDataShareFixture), ShareFixture = true)] + [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to true THEN same fixture per data row is used.")] public void GivenMemberAutoMockData_WhenShareFixtureIsSetToTrue_ThenSameFixturePerDataRowIsUsed(int index, ICustomization customization, IFixture fixture) { // Arrange @@ -125,8 +125,8 @@ public void GivenMemberAutoMockData_WhenShareFixtureIsSetToTrue_ThenSameFixtureP fixture.Customizations.Should().HaveCount(expectedCustomizationsCount + index); } - [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to false THEN unique fixture per data row is created.")] [MemberAutoMockData(nameof(TestDataDoNotShareFixture), ShareFixture = false)] + [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to false THEN unique fixture per data row is created.")] public void GivenMemberAutoMockData_WhenShareFixtureIsSetToFalse_ThenUniqueFixturePerDataRowIsCreated(ICustomization customization, IFixture fixture) { // Arrange @@ -162,8 +162,8 @@ public void GivenUninitializedMemberName_WhenConstructorIsInvoked_ThenExceptionI Assert.Throws(() => new MemberAutoMockDataAttribute(memberName)); } - [Theory(DisplayName = "GIVEN test method has some member generated parameters WHEN test run THEN parameters are provided")] [MemberAutoMockData(nameof(TestData))] + [Theory(DisplayName = "GIVEN test method has some member generated parameters WHEN test run THEN parameters are provided")] public void GivenTestMethodHasSomeMemberGeneratedParameters_WhenTestRun_ThenParametersAreProvided( int first, int second, diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/AutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/AutoMockDataAttributeTests.cs index 968b3746..865e4bfa 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/AutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/AutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit.Sdk; [Collection("AutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class AutoMockDataAttributeTests { [Fact(DisplayName = "WHEN parameterless constructor is invoked THEN fixture and attribute provider are created")] @@ -34,9 +34,9 @@ public void WhenParameterlessConstructorIsInvoked_ThenFixtureAndAttributeProvide attribute.IgnoreVirtualMembers.Should().BeFalse(); } - [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] public void WhenGetDataIsInvoked_ThenFixtureIsConfiguredAndDataReturned(bool ignoreVirtualMembers) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/InlineAutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/InlineAutoMockDataAttributeTests.cs index 0b690c6c..dad294bb 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/InlineAutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/InlineAutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit.Sdk; [Collection("InlineAutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class InlineAutoMockDataAttributeTests { [Fact(DisplayName = "WHEN parameterless constructor is invoked THEN has no values but fixture and attribute provider are created")] @@ -67,9 +67,9 @@ public void GivenUninitializedValues_WhenConstructorIsInvoked_ThenHasNoValuesAnd attribute.Values.Should().HaveCount(0); } - [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] public void WhenGetDataIsInvoked_ThenFixtureIsConfiguredAndDataReturned(bool ignoreVirtualMembers) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/MemberAutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/MemberAutoMockDataAttributeTests.cs index 96084437..56d2ee8b 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/MemberAutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoMoq.Tests/Attributes/MemberAutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit; [Collection("MemberAutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class MemberAutoMockDataAttributeTests { public static IEnumerable TestData { get; } = new[] @@ -42,8 +42,8 @@ public int TestMethod(int first, int second, int third, int fourth, IDisposable return first + second + third + fourth; } - [Theory(DisplayName = "WHEN constructor is invoked THEN has shared fixture")] [AutoData] + [Theory(DisplayName = "WHEN constructor is invoked THEN has shared fixture")] public void WhenConstructorIsInvoked_ThenHasSharedFixture(Fixture fixture) { // Arrange @@ -80,9 +80,9 @@ public void GivenExistingMemberName_WhenGetDataIsInvoked_ThenAppropriateDataIsRe } } - [Theory(DisplayName = "GIVEN IgnoreVirtualMembers WHEN GetData is invoked THEN fixture is customized correctly")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "GIVEN IgnoreVirtualMembers WHEN GetData is invoked THEN fixture is customized correctly")] public void GivenIgnoreVirtualMembers_WhenGetDataIsInvoked_ThenFixtureIsCustomizedCorrectly(bool ignoreVirtualMembers) { // Arrange @@ -111,8 +111,8 @@ public void GivenIgnoreVirtualMembers_WhenGetDataIsInvoked_ThenFixtureIsCustomiz customizations[1].Should().BeOfType(); } - [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to true THEN same fixture per data row is used.")] [MemberAutoMockData(nameof(TestDataShareFixture), ShareFixture = true)] + [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to true THEN same fixture per data row is used.")] public void GivenMemberAutoMockData_WhenShareFixtureIsSetToTrue_ThenSameFixturePerDataRowIsUsed(int index, ICustomization customization, IFixture fixture) { // Arrange @@ -125,8 +125,8 @@ public void GivenMemberAutoMockData_WhenShareFixtureIsSetToTrue_ThenSameFixtureP fixture.Customizations.Should().HaveCount(expectedCustomizationsCount + index); } - [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to false THEN unique fixture per data row is created.")] [MemberAutoMockData(nameof(TestDataDoNotShareFixture), ShareFixture = false)] + [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to false THEN unique fixture per data row is created.")] public void GivenMemberAutoMockData_WhenShareFixtureIsSetToFalse_ThenUniqueFixturePerDataRowIsCreated(ICustomization customization, IFixture fixture) { // Arrange @@ -162,8 +162,8 @@ public void GivenUninitializedMemberName_WhenConstructorIsInvoked_ThenExceptionI Assert.Throws(() => new MemberAutoMockDataAttribute(memberName)); } - [Theory(DisplayName = "GIVEN test method has some member generated parameters WHEN test run THEN parameters are provided")] [MemberAutoMockData(nameof(TestData))] + [Theory(DisplayName = "GIVEN test method has some member generated parameters WHEN test run THEN parameters are provided")] public void GivenTestMethodHasSomeMemberGeneratedParameters_WhenTestRun_ThenParametersAreProvided( int first, int second, diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/AutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/AutoMockDataAttributeTests.cs index 1112f003..38db05f6 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/AutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/AutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit.Sdk; [Collection("AutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class AutoMockDataAttributeTests { [Fact(DisplayName = "WHEN parameterless constructor is invoked THEN fixture and attribute provider are created")] @@ -34,9 +34,9 @@ public void WhenParameterlessConstructorIsInvoked_ThenFixtureAndAttributeProvide attribute.IgnoreVirtualMembers.Should().BeFalse(); } - [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] public void WhenGetDataIsInvoked_ThenFixtureIsConfiguredAndDataReturned(bool ignoreVirtualMembers) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/InlineAutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/InlineAutoMockDataAttributeTests.cs index c1dfac7d..126b2def 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/InlineAutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/InlineAutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit.Sdk; [Collection("InlineAutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class InlineAutoMockDataAttributeTests { [Fact(DisplayName = "WHEN parameterless constructor is invoked THEN has no values but fixture and attribute provider are created")] @@ -67,9 +67,9 @@ public void GivenUninitializedValues_WhenConstructorIsInvoked_ThenHasNoValuesAnd attribute.Values.Should().HaveCount(0); } - [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "WHEN GetData is invoked THEN fixture is configured and data returned")] public void WhenGetDataIsInvoked_ThenFixtureIsConfiguredAndDataReturned(bool ignoreVirtualMembers) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/MemberAutoMockDataAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/MemberAutoMockDataAttributeTests.cs index 4b2a09da..4afe7a0b 100644 --- a/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/MemberAutoMockDataAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Tests/Attributes/MemberAutoMockDataAttributeTests.cs @@ -18,7 +18,7 @@ using Xunit; [Collection("MemberAutoMockDataAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class MemberAutoMockDataAttributeTests { public static IEnumerable TestData { get; } = new[] @@ -42,8 +42,8 @@ public int TestMethod(int first, int second, int third, int fourth, IDisposable return first + second + third + fourth; } - [Theory(DisplayName = "WHEN constructor is invoked THEN has shared fixture")] [AutoData] + [Theory(DisplayName = "WHEN constructor is invoked THEN has shared fixture")] public void WhenConstructorIsInvoked_ThenHasSharedFixture(Fixture fixture) { // Arrange @@ -80,9 +80,9 @@ public void GivenExistingMemberName_WhenGetDataIsInvoked_ThenAppropriateDataIsRe } } - [Theory(DisplayName = "GIVEN IgnoreVirtualMembers WHEN GetData is invoked THEN fixture is customized correctly")] [InlineAutoData(true)] [InlineAutoData(false)] + [Theory(DisplayName = "GIVEN IgnoreVirtualMembers WHEN GetData is invoked THEN fixture is customized correctly")] public void GivenIgnoreVirtualMembers_WhenGetDataIsInvoked_ThenFixtureIsCustomizedCorrectly(bool ignoreVirtualMembers) { // Arrange @@ -110,8 +110,8 @@ public void GivenIgnoreVirtualMembers_WhenGetDataIsInvoked_ThenFixtureIsCustomiz customizations[1].Should().BeOfType(); } - [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to true THEN same fixture per data row is used.")] [MemberAutoMockData(nameof(TestDataShareFixture), ShareFixture = true)] + [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to true THEN same fixture per data row is used.")] public void GivenMemberAutoMockData_WhenShareFixtureIsSetToTrue_ThenSameFixturePerDataRowIsUsed(int index, ICustomization customization, IFixture fixture) { // Arrange @@ -124,8 +124,8 @@ public void GivenMemberAutoMockData_WhenShareFixtureIsSetToTrue_ThenSameFixtureP fixture.Customizations.Should().HaveCount(expectedCustomizationsCount + index); } - [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to false THEN unique fixture per data row is created.")] [MemberAutoMockData(nameof(TestDataDoNotShareFixture), ShareFixture = false)] + [Theory(DisplayName = "GIVEN MemberAutoMockData WHEN ShareFixture is set to false THEN unique fixture per data row is created.")] public void GivenMemberAutoMockData_WhenShareFixtureIsSetToFalse_ThenUniqueFixturePerDataRowIsCreated(ICustomization customization, IFixture fixture) { // Arrange @@ -161,8 +161,8 @@ public void GivenUninitializedMemberName_WhenConstructorIsInvoked_ThenExceptionI Assert.Throws(() => new MemberAutoMockDataAttribute(memberName)); } - [Theory(DisplayName = "GIVEN test method has some member generated parameters WHEN test run THEN parameters are provided")] [MemberAutoMockData(nameof(TestData))] + [Theory(DisplayName = "GIVEN test method has some member generated parameters WHEN test run THEN parameters are provided")] public void GivenTestMethodHasSomeMemberGeneratedParameters_WhenTestRun_ThenParametersAreProvided( int first, int second, diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/.editorconfig b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/.editorconfig index d0121036..066f41c6 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/.editorconfig +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/.editorconfig @@ -12,6 +12,8 @@ dotnet_diagnostic.CA1707.severity = none dotnet_diagnostic.CA1822.severity = none # CA1859: Use concrete types when possible for improved performance dotnet_diagnostic.CA1859.severity = none +# CA1861: Avoid constant arrays as arguments +dotnet_diagnostic.CA1861.severity = none # CA2000: Dispose objects before losing scope dotnet_diagnostic.CA2000.severity = none # CA2007: Consider calling ConfigureAwait on the awaited task diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataAdapterAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataAdapterAttributeTests.cs index 00fc2fc3..f516c8a9 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataAdapterAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataAdapterAttributeTests.cs @@ -15,12 +15,12 @@ using Xunit; [Collection("AutoDataAdapterAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Test objects")] public class AutoDataAdapterAttributeTests { - [Theory(DisplayName = "GIVEN fixture WHEN constructor is invoked THEN passed fixture is being adapted and inline values collection is empty")] [AutoData] + [Theory(DisplayName = "GIVEN fixture WHEN constructor is invoked THEN passed fixture is being adapted and inline values collection is empty")] public void GivenFixture_WhenConstructorIsInvoked_ThenPassedFixtureIsBeingAdaptedAndInlineValuesCollectionIsEmpty(Fixture fixture) { // Arrange @@ -41,8 +41,8 @@ public void GivenUninitializedFixture_WhenConstructorIsInvoked_ThenExceptionIsTh Assert.Throws(() => new AutoDataAdapterAttribute(null)); } - [Theory(DisplayName = "GIVEN uninitialized method info WHEN GetData is invoked THEN exception is thrown")] [AutoData] + [Theory(DisplayName = "GIVEN uninitialized method info WHEN GetData is invoked THEN exception is thrown")] public void GivenUninitializedMethodInfo_WhenConstructorIsInvoked_ThenExceptionIsThrown(Fixture fixture) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataBaseAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataBaseAttributeTests.cs index 9fbdb6ac..7a52a15c 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataBaseAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/AutoDataBaseAttributeTests.cs @@ -14,11 +14,11 @@ using Xunit; [Collection("AutoDataBaseAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class AutoDataBaseAttributeTests { - [Theory(DisplayName = "GIVEN existing fixture and attribute provider WHEN constructor is invoked THEN has specified fixture and attribute provider")] [AutoData] + [Theory(DisplayName = "GIVEN existing fixture and attribute provider WHEN constructor is invoked THEN has specified fixture and attribute provider")] public void GivenExistingFixtureAndAttributeProvider_WhenConstructorIsInvoked_ThenHasSpecifiedFixtureAndAttributeProvider(Fixture fixture) { // Arrange @@ -45,8 +45,8 @@ public void GivenUninitializedFixture_WhenConstructorIsInvoked_ThenExceptionIsTh Assert.Throws(() => new AutoDataBaseAttributeUnderTest(fixture, provider.Object)); } - [Theory(DisplayName = "GIVEN uninitialized attribute provider WHEN constructor is invoked THEN exception is thrown")] [AutoData] + [Theory(DisplayName = "GIVEN uninitialized attribute provider WHEN constructor is invoked THEN exception is thrown")] public void GivenUninitializedAttributeProvider_WhenConstructorIsInvoked_ThenExceptionIsThrown(Fixture fixture) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/CustomizeWithAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/CustomizeWithAttributeTests.cs index a1d84046..8a441fbd 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/CustomizeWithAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/CustomizeWithAttributeTests.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -10,13 +11,14 @@ using global::AutoFixture; using global::AutoFixture.Kernel; using global::AutoFixture.Xunit2; + using Objectivity.AutoFixture.XUnit2.Core.Attributes; using Objectivity.AutoFixture.XUnit2.Core.Customizations; using Xunit; [Collection("CustomizeWithAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "CustomizeAttribute")] public class CustomizeWithAttributeTests { public static IEnumerable ArgumentsDiscoveryCustomizationTestData { get; } = new[] @@ -123,9 +125,9 @@ public void GivenCustomizeWithAttributeWithIncludeParameterTypeSet_WhenGetCustom customization.Args.Should().HaveCount(1).And.Subject.First().Should().Be(parameter.ParameterType); } - [Theory(DisplayName = "GIVEN CustomizeWith attribute with arguments WHEN GetCustomization is invoked THEN expected customization is returned with expected numner of arguments")] [MemberData(nameof(ArgumentsDiscoveryCustomizationTestData))] - public void GivenCustomizeWithAttributeWithArguments_WhenGetCustomizationIsInvoked_ThenExpectedCustomizationIsReturnedWithExpectedNumnerOfArguments(bool includeParameterType, object[] args, int expectedNumberOfArguments) + [Theory(DisplayName = "GIVEN CustomizeWith attribute with arguments WHEN GetCustomization is invoked THEN expected customization is returned with expected number of arguments")] + public void GivenCustomizeWithAttributeWithArguments_WhenGetCustomizationIsInvoked_ThenExpectedCustomizationIsReturnedWithExpectedNumberOfArguments(bool includeParameterType, object[] args, int expectedNumberOfArguments) { // Arrange var customizationType = typeof(ArgumentsDiscoveryCustomization); @@ -143,8 +145,8 @@ public void GivenCustomizeWithAttributeWithArguments_WhenGetCustomizationIsInvok customization.Args.Should().HaveCount(expectedNumberOfArguments); } - [Theory(DisplayName = "GIVEN CustomizeWith applied to the second argument WHEN data populated THEN only second one has customization")] [AutoData] + [Theory(DisplayName = "GIVEN CustomizeWith applied to the second argument WHEN data populated THEN only second one has customization")] public void GivenCustomizeWithAppliedToTheSecondArgument_WhenDataPopulated_ThenOnlySecondOneHasCustomization( IList instanceWithoutCustomization, [EmptyCollection] IList instanceWithCustomization) @@ -156,8 +158,8 @@ public void GivenCustomizeWithAppliedToTheSecondArgument_WhenDataPopulated_ThenO instanceWithCustomization.Should().BeEmpty(); } - [Theory(DisplayName = "GIVEN CustomizeWith applied to the first argument WHEN data populated THEN all arguments has customization")] [AutoData] + [Theory(DisplayName = "GIVEN CustomizeWith applied to the first argument WHEN data populated THEN all arguments has customization")] public void GivenCustomizeWithAppliedToTheFirstArgument_WhenDataPopulated_ThenAllArgumentsHasCustomization( [CustomizeWith(typeof(EmptyCollectionCustomization), typeof(IList))] IList instanceWithCustomization, IList instanceWithoutCustomization) @@ -169,9 +171,9 @@ public void GivenCustomizeWithAppliedToTheFirstArgument_WhenDataPopulated_ThenAl instanceWithCustomization.Should().BeEmpty(); } - [Theory(DisplayName = "GIVEN CustomizeWith applied to the first argument of a cecrtain type WHEN data populated THEN only the first one has customization")] [AutoData] - public void GivenCustomizeWithAppliedToTheFirstArgumentOfACecrtainType_WhenDataPopulated_ThenOnlyTheFirstOneHasCustomization( + [Theory(DisplayName = "GIVEN CustomizeWith applied to the first argument of a certain type WHEN data populated THEN only the first one has customization")] + public void GivenCustomizeWithAppliedToTheFirstArgumentOfACertainType_WhenDataPopulated_ThenOnlyTheFirstOneHasCustomization( [EmptyCollection] IList instanceWithCustomization, IList instanceOfDifferentTypeWithoutCustomization) { @@ -182,8 +184,8 @@ public void GivenCustomizeWithAppliedToTheFirstArgumentOfACecrtainType_WhenDataP instanceOfDifferentTypeWithoutCustomization.Should().NotBeEmpty(); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1163:Unused parameter.", Justification = "Required for test")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Required for test")] + [SuppressMessage("Roslynator", "RCS1163:Unused parameter.", Justification = "Required for test")] + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Required for test")] protected void MethodUnderTest(bool parameter) { // Empty method under test diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/ExceptAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/ExceptAttributeTests.cs new file mode 100644 index 00000000..c0c9df5d --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/ExceptAttributeTests.cs @@ -0,0 +1,187 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Attributes +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using FluentAssertions; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using global::AutoFixture.Xunit2; + + using Moq; + + using Objectivity.AutoFixture.XUnit2.Core.Attributes; + + using Xunit; + + [Collection("ExceptAttribute")] + [Trait("Category", "CustomizeAttribute")] + public class ExceptAttributeTests + { + public enum Numbers + { + None = 0, + One = 1, + Two = 2, + Three = 4, + Four = 8, + Five = 16, + } + + public static IEnumerable CustomizationUsageTestData { get; } = new[] + { + new object[] { 1 }, + new object[] { 1.5f }, + new object[] { "test" }, + new object[] { false }, + new object[] { Numbers.Five }, + new object[] { DateTime.Now }, + new object[] { ValueTuple.Create(5) }, + new object[] { Tuple.Create(1, 2) }, + }; + + [Fact(DisplayName = "GIVEN uninitialized argument WHEN constructor is invoked THEN exception is thrown")] + public void GivenUninitializedArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new ExceptAttribute(null)); + } + + [Fact(DisplayName = "GIVEN no arguments WHEN constructor is invoked THEN exception is thrown")] + public void GivenNoArguments_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new ExceptAttribute()); + } + + [InlineData(1, 1)] + [InlineData("a", "a")] + [Theory(DisplayName = "GIVEN identical arguments WHEN constructor is invoked THEN unique parameters are properly assigned")] + public void GivenIdenticalArguments_WhenConstructorIsInvoked_ThenUniqueParametersAreProperlyAssigned( + object first, + object second) + { + // Arrange + var attribute = new ExceptAttribute(first, second); + + // Assert + attribute.Values.Should().HaveCount(1).And.BeEquivalentTo(new[] { first }); + } + + [InlineData(typeof(int), 2)] + [InlineData(1, typeof(int))] + [Theory(DisplayName = "GIVEN incomparable argument WHEN constructor is invoked THEN parameters are properly assigned")] + public void GivenIncomparableArgument_WhenConstructorIsInvoked_ThenParametersAreProperlyAssigned( + object first, + object second) + { + // Arrange + // Act + var attribute = new ExceptAttribute(first, second); + + // Assert + attribute.Values.Should().HaveCount(2).And.BeEquivalentTo(new[] { first, second }); + } + + [MemberData(nameof(CustomizationUsageTestData))] + [Theory(DisplayName = "GIVEN valid parameters WHEN customization is used THEN expected values are generated")] + public void GivenValidParameters_WhenCustomizationIsUsed_ThenExpectedValuesAreGenerated( + object item) + { + // Arrange + var attribute = new ExceptAttribute(item); + var request = new Mock(); + var expectedType = item.GetType(); + request.SetupGet(x => x.ParameterType) + .Returns(expectedType); + IFixture fixture = new Fixture(); + fixture.Customize(attribute.GetCustomization(request.Object)); + + // Act + var result = fixture.Create(request.Object, new SpecimenContext(fixture)); + + // Assert + result.Should().NotBeNull() + .And.BeOfType(expectedType) + .And.NotBe(item); + } + + [AutoData] + [Theory(DisplayName = "GIVEN multiple values specified but one WHEN values populated THEN the one value is used")] + public void GivenMultipleValuesSpecifiedButOne_WhenValuesPopulated_ThenTheOneValueIsUsed( + [Except( + Numbers.None, + Numbers.One, + Numbers.Two, + Numbers.Three, + Numbers.Four)] Numbers[] targetValues) + { + // Arrange + // Act + // Assert + targetValues.Should().AllSatisfy(x => x.Should().Be(Numbers.Five)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified only for one set WHEN values populated THEN the other set is not impacted")] + public void GivenValuesSpecifiedOnlyForOneSet_WhenValuesPopulated_ThenTheOtherSetIsNotImpacted( + [Except( + Numbers.None, + Numbers.One, + Numbers.Two, + Numbers.Three, + Numbers.Four)] Numbers[] firstSet, + Numbers[] secondSet) + { + // Arrange + // Act + // Assert + firstSet.Should().AllSatisfy(x => x.Should().Be(Numbers.Five)); + secondSet.Where(x => x != Numbers.Five).Should().HaveCountGreaterThan(1); + } + + [AutoData] + [Theory(DisplayName = "GIVEN multiple values specified but one WHEN values populated THEN generated sets have no common part")] + public void GivenDifferentValuesSpecifiedForDifferentSets_WhenValuesPopulated_ThenGeneratedSetsHaveNoCommonPart( + [Except( + Numbers.None, + Numbers.One, + Numbers.Two)] Numbers[] firstSet, + [Except( + Numbers.Three, + Numbers.Four, + Numbers.Five)] Numbers[] secondSet) + { + // Arrange + // Act + // Assert + firstSet.Should().AllSatisfy(x => x.Should().BeOneOf(Numbers.Three, Numbers.Four, Numbers.Five)); + secondSet.Should().AllSatisfy(x => x.Should().BeOneOf(Numbers.None, Numbers.One, Numbers.Two)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified only for one set WHEN values populated THEN the other set is not impacted")] + public void GivenValuesSpecifiedForFirstValueAndFrozen_WhenValuesPopulated_ThenBothValuesAreImpacted( + [Frozen] [Except( + Numbers.None, + Numbers.One, + Numbers.Two, + Numbers.Three, + Numbers.Four)] Numbers firstValue, + Numbers secondValue) + { + // Arrange + // Act + // Assert + firstValue.Should().Be(Numbers.Five); + secondValue.Should().Be(firstValue); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/IgnoreVirtualMembersAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/IgnoreVirtualMembersAttributeTests.cs index 7151f8f8..29a6caca 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/IgnoreVirtualMembersAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/IgnoreVirtualMembersAttributeTests.cs @@ -11,7 +11,7 @@ using Xunit; [Collection("IgnoreVirtualMembersAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "CustomizeAttribute")] [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Test objects")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Test classes instantiated by AutoFixture.")] [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Test members instantiated by AutoFixture and used in tests.")] diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/InlineAutoDataBaseAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/InlineAutoDataBaseAttributeTests.cs index 53d0848f..a4a53f2a 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/InlineAutoDataBaseAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/InlineAutoDataBaseAttributeTests.cs @@ -14,11 +14,11 @@ using Xunit; [Collection("InlineAutoDataBaseAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class InlineMockDataBaseAttributeTests { - [Theory(DisplayName = "GIVEN existing fixture and attribute provider WHEN constructor is invoked THEN has fixture attribute provider and no values")] [AutoData] + [Theory(DisplayName = "GIVEN existing fixture and attribute provider WHEN constructor is invoked THEN has fixture attribute provider and no values")] public void GivenExistingFixtureAndAttributeProvider_WhenConstructorIsInvoked_ThenHasFixtureAttributeProviderAndNoValues(Fixture fixture) { // Arrange @@ -34,8 +34,8 @@ public void GivenExistingFixtureAndAttributeProvider_WhenConstructorIsInvoked_Th attribute.Values.Should().HaveCount(0); } - [Theory(DisplayName = "GIVEN existing fixture, attribute provider and values WHEN constructor is invoked THEN has specified fixture, attribute provider and values")] [AutoData] + [Theory(DisplayName = "GIVEN existing fixture, attribute provider and values WHEN constructor is invoked THEN has specified fixture, attribute provider and values")] public void GivenExistingFixtureAttributeProviderAndValues_WhenConstructorIsInvoked_ThenHasSpecifiedFixtureAttributeProviderAndValues(Fixture fixture) { // Arrange @@ -52,8 +52,8 @@ public void GivenExistingFixtureAttributeProviderAndValues_WhenConstructorIsInvo attribute.Values.Should().BeEquivalentTo(initialValues); } - [Theory(DisplayName = "GIVEN existing fixture, attribute provider and uninitialized values WHEN constructor is invoked THEN has specified fixture, attribute provider and no values")] [AutoData] + [Theory(DisplayName = "GIVEN existing fixture, attribute provider and uninitialized values WHEN constructor is invoked THEN has specified fixture, attribute provider and no values")] public void GivenExistingFixtureAttributeProviderAndUninitializedValues_WhenConstructorIsInvoked_ThenHasSpecifiedFixtureAttributeProviderAndNoValues(Fixture fixture) { // Arrange @@ -82,8 +82,8 @@ public void GivenUninitializedFixture_WhenConstructorIsInvoked_ThenExceptionIsTh Assert.Throws(() => new InlineAutoDataBaseAttributeUnderTest(fixture, provider.Object)); } - [Theory(DisplayName = "GIVEN uninitialized attribute provider WHEN constructor is invoked THEN exception is thrown")] [AutoData] + [Theory(DisplayName = "GIVEN uninitialized attribute provider WHEN constructor is invoked THEN exception is thrown")] public void GivenUninitializedAttributeProvider_WhenConstructorIsInvoked_ThenExceptionIsThrown(Fixture fixture) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/MemberAutoDataBaseAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/MemberAutoDataBaseAttributeTests.cs index 1a69dc96..a79c7b06 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/MemberAutoDataBaseAttributeTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/MemberAutoDataBaseAttributeTests.cs @@ -12,11 +12,11 @@ using Xunit; [Collection("MemberAutoDataBaseAttribute")] - [Trait("Category", "Attributes")] + [Trait("Category", "DataAttribute")] public class MemberAutoDataBaseAttributeTests { - [Theory(DisplayName = "GIVEN uninitialized fixture WHEN constructor is invoked THEN exception is thrown")] [AutoData] + [Theory(DisplayName = "GIVEN uninitialized fixture WHEN constructor is invoked THEN exception is thrown")] public void GivenUninitializedFixture_WhenConstructorIsInvoked_ThenExceptionIsThrown(string memberName) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromRangeAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromRangeAttributeTests.cs new file mode 100644 index 00000000..d411ec10 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromRangeAttributeTests.cs @@ -0,0 +1,306 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Attributes +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics.CodeAnalysis; + + using FluentAssertions; + + using global::AutoFixture.Xunit2; + + using Objectivity.AutoFixture.XUnit2.Core.Attributes; + + using Xunit; + + [Collection("PickFromRangeAttribute")] + [Trait("Category", "CustomizeAttribute")] + public class PickFromRangeAttributeTests + { + public static IEnumerable MemberAutoDataOverValuesTestData { get; } = new[] + { + new object[] { 10, 10 }, + }; + + [Fact(DisplayName = "GIVEN minimum greater than maximum WHEN constructor is invoked THEN exception is thrown")] + public void GivenMinimumGreaterThanMaximum_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + const int min = 100; + const int max = 1; + + // Act + // Assert + Assert.Throws(() => new PickFromRangeAttribute(min, max)); + } + + [Fact(DisplayName = "GIVEN valid parameters WHEN constructor is invoked THEN parameters are properly assigned")] + public void GivenValidParameters_WhenConstructorIsInvoked_ThenParametersAreProperlyAssigned() + { + // Arrange + const int min = 1; + const int max = 100; + + // Act + var range = new PickFromRangeAttribute(min, max); + + // Assert + range.Maximum.Should().NotBeNull().And.Be(max); + range.Minimum.Should().NotBeNull().And.Be(min); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN byte populated THEN the value from range is generated")] + public void GivenRangeSpecified_WhenBytePopulated_ThenTheValueFromRangeIsGenerated( + [PickFromRange(byte.MaxValue - 10, byte.MaxValue)] byte rangeValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(byte.MaxValue - 10, byte.MaxValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN unsigned short populated THEN the value from range is generated")] + public void GivenRangeSpecified_WhenUShortPopulated_ThenTheValueFromRangeIsGenerated( + [PickFromRange(ushort.MaxValue - 10, ushort.MaxValue)] ushort rangeValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(ushort.MaxValue - 10, ushort.MaxValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN unsigned integer populated THEN the value from range is generated")] + public void GivenRangeSpecified_WhenUIntPopulated_ThenTheValueFromRangeIsGenerated( + [PickFromRange(uint.MaxValue - 10, uint.MaxValue)] uint rangeValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(uint.MaxValue - 10, uint.MaxValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN unsigned long populated THEN the value from range is generated")] + public void GivenRangeSpecified_WhenULongPopulated_ThenTheValueFromRangeIsGenerated( + [PickFromRange(ulong.MaxValue - 10, ulong.MaxValue)] ulong rangeValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(ulong.MaxValue - 10, ulong.MaxValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN signed byte populated THEN the value from range is generated")] + public void GivenRangeSpecified_WhenSBytePopulated_ThenTheValueFromRangeIsGenerated( + [PickFromRange(sbyte.MaxValue - 10, sbyte.MaxValue)] sbyte rangeValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(sbyte.MaxValue - 10, sbyte.MaxValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN short populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenShortPopulated_ThenOnlyDecoratedParameterHasValueFromRange( + [PickFromRange(short.MinValue, short.MinValue + 10)] short rangeValue, + short unrestrictedValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(short.MinValue, short.MinValue + 10); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN integer populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenIntPopulated_ThenOnlyDecoratedParameterHasValueFromRange( + [PickFromRange(int.MinValue, sbyte.MinValue)] int rangeValue, + int unrestrictedValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(int.MinValue, sbyte.MinValue); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN long populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenLongPopulated_ThenOnlyDecoratedParameterHasValueFromRange( + [PickFromRange(long.MinValue, long.MinValue + 10)] long rangeValue, + long unrestrictedValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(long.MinValue, long.MinValue + 10); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN float populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenFloatPopulated_ThenOnlyDecoratedParameterHasValueFromRange( + [PickFromRange(float.MinValue, float.MinValue + 10)] float rangeValue, + float unrestrictedValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(float.MinValue, float.MinValue + 10); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN double populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenDoublePopulated_ThenOnlyDecoratedParameterHasValueFromRange( + [PickFromRange(double.MinValue, double.MinValue + 10)] double rangeValue, + double unrestrictedValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(double.MinValue, double.MinValue + 10); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN decimal populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenDecimalPopulated_ThenOnlyDecoratedParameterHasValueFromRange( + [PickFromRange(-12.3, -4.5)] decimal rangeValue, + decimal unrestrictedValue) + { + // Arrange + // Act + // Assert + rangeValue.Should().BeInRange(-12.3m, -4.5m); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [InlineAutoData(10, 10)] + [InlineAutoData(0, 0)] + [Theory(DisplayName = "GIVEN range specified and inline value outside range WHEN data populated THEN values from range are ignored and inline one is used")] + public void GivenRangeSpecifiedAndInlineValueOutsideRange_WhenDataPopulated_ThenValuesFromRangeAreIgnoredAndInlineOneIsUsed( + [PickFromRange(int.MinValue, short.MinValue)] int value, + int expectedResult, + [PickFromRange(short.MinValue, sbyte.MinValue)] int unrestrictedValue) + { + // Arrange + // Act + // Assert + value.Should().Be(expectedResult).And.NotBeInRange(int.MinValue, sbyte.MinValue); + unrestrictedValue.Should().BeInRange(short.MinValue, sbyte.MinValue); + } + + [MemberAutoData(nameof(MemberAutoDataOverValuesTestData))] + [Theory(DisplayName = "GIVEN range specified and member data value outside range WHEN data populated THEN values from range are ignored and member data is used")] + public void GivenRangeSpecifiedAndMemberDataValueOutsideRange_WhenDataPopulated_ThenValuesFromRangeAreIgnoredAndMemberDataIsUsed( + [PickFromRange(int.MinValue, sbyte.MinValue)] int value, + int expectedResult, + [PickFromRange(int.MinValue, sbyte.MinValue)] int unrestrictedValue) + { + // Arrange + // Act + // Assert + value.Should().Be(expectedResult).And.NotBeInRange(int.MinValue, sbyte.MinValue); + unrestrictedValue.Should().BeInRange(int.MinValue, sbyte.MinValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN arrays populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenArraysPopulated_ThenOnlyDecoratedParameterHasValuesFromRange( + [PickFromRange(int.MinValue, sbyte.MinValue)] int[] rangeValues, + int[] unrestrictedValues) + { + // Arrange + // Act + // Assert + rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(int.MinValue, sbyte.MinValue)); + unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN enumerable collections populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenEnumerableCollectionsPopulated_ThenOnlyDecoratedParameterHasValuesFromRange( + [PickFromRange(int.MinValue, sbyte.MinValue)] IEnumerable rangeValues, + IEnumerable unrestrictedValues) + { + // Arrange + // Act + // Assert + rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(int.MinValue, sbyte.MinValue)); + unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN lists populated THEN only decorated parameter has value from range")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We are testing generic lists")] + public void GivenRangeSpecified_WhenListPopulated_ThenOnlyDecoratedParameterHasValuesFromRange( + [PickFromRange(int.MinValue, sbyte.MinValue)] List rangeValues, + List unrestrictedValues) + { + // Arrange + // Act + // Assert + rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(int.MinValue, sbyte.MinValue)); + unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN sets populated THEN only decorated parameter has value from range")] + public void GivenRangeSpecified_WhenSetsPopulated_ThenOnlyDecoratedParameterHasValuesFromRange( + [PickFromRange(int.MinValue, sbyte.MinValue)] HashSet rangeValues, + HashSet unrestrictedValues) + { + // Arrange + // Act + // Assert + rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(int.MinValue, sbyte.MinValue)); + unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN collections populated THEN only decorated parameter has value from range")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We are testing generic lists")] + public void GivenRangeSpecified_WhenCollectionsPopulated_ThenOnlyDecoratedParameterHasValuesFromRange( + [PickFromRange(int.MinValue, sbyte.MinValue)] Collection rangeValues, + Collection unrestrictedValues) + { + // Arrange + // Act + // Assert + rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(int.MinValue, sbyte.MinValue)); + unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range specified WHEN read-only collections populated THEN only decorated parameter has value from range")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We are testing generic lists")] + public void GivenRangeSpecified_WhenReadOnlyCollectionsPopulated_ThenOnlyDecoratedParameterHasValuesFromRange( + [PickFromRange(int.MinValue, sbyte.MinValue)] ReadOnlyCollection rangeValues, + ReadOnlyCollection unrestrictedValues) + { + // Arrange + // Act + // Assert + rangeValues.Should().AllSatisfy(x => x.Should().BeInRange(int.MinValue, sbyte.MinValue)); + unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN range with single value WHEN array populated THEN all values equal specified one")] + public void GivenRangeWithSingleValue_WhenArrayPopulated_ThenAllValuesEqualSpecifiedOne( + [PickFromRange(int.MinValue, int.MinValue)] int[] rangeValues) + { + // Arrange + // Act + // Assert + rangeValues.Should().HaveCountGreaterThan(1).And.AllSatisfy(x => x.Should().Be(int.MinValue)); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromValuesAttributeTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromValuesAttributeTests.cs new file mode 100644 index 00000000..37134f74 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Attributes/PickFromValuesAttributeTests.cs @@ -0,0 +1,317 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Attributes +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using FluentAssertions; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using global::AutoFixture.Xunit2; + + using Moq; + + using Objectivity.AutoFixture.XUnit2.Core.Attributes; + + using Xunit; + + [Collection("PickFromValuesAttribute")] + [Trait("Category", "CustomizeAttribute")] + public class PickFromValuesAttributeTests + { + [Flags] + public enum Numbers + { + None = 0, + One = 1, + Two = 2, + Three = 4, + Four = 8, + Five = 16, + } + + public static IEnumerable MemberAutoDataOverValuesTestData { get; } = new[] + { + new object[] { 10, 10 }, + }; + + public static IEnumerable CustomizationUsageTestData { get; } = new[] + { + new object[] { 1, 1, 2 }, + new object[] { 1.5f, 1.5f, -0.3f }, + new object[] { string.Empty, "a", "b" }, + new object[] { false, false }, + new object[] { DateTime.Now, DateTime.Now, DateTime.MinValue }, + new object[] { Numbers.Five, Numbers.Five, Numbers.One }, + new object[] { ValueTuple.Create(5), ValueTuple.Create(5), ValueTuple.Create(-3) }, + new object[] { Tuple.Create(1, 2), Tuple.Create(1, 2), Tuple.Create(-1, 0) }, + new object[] { new(), 1, 1.5f }, + }; + + [Fact(DisplayName = "GIVEN uninitialized argument WHEN constructor is invoked THEN exception is thrown")] + public void GivenUninitializedArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new PickFromValuesAttribute(null)); + } + + [Fact(DisplayName = "GIVEN no arguments WHEN constructor is invoked THEN exception is thrown")] + public void GivenNoArguments_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new PickFromValuesAttribute()); + } + + [InlineData(1, 1)] + [InlineData("a", "a")] + [Theory(DisplayName = "GIVEN identical arguments WHEN constructor is invoked THEN unique parameters are properly assigned")] + public void GivenIdenticalArguments_WhenConstructorIsInvoked_ThenUniqueParametersAreProperlyAssigned( + object first, + object second) + { + // Arrange + var attribute = new PickFromValuesAttribute(first, second); + + // Assert + attribute.Values.Should().HaveCount(1).And.BeEquivalentTo(new[] { first }); + } + + [InlineData(typeof(int), 2)] + [InlineData(1, typeof(int))] + [Theory(DisplayName = "GIVEN incomparable argument WHEN constructor is invoked THEN parameters are properly assigned")] + public void GivenIncomparableArgument_WhenConstructorIsInvoked_ThenParametersAreProperlyAssigned( + object first, + object second) + { + // Arrange + // Act + var attribute = new PickFromValuesAttribute(first, second); + + // Assert + attribute.Values.Should().HaveCount(2).And.BeEquivalentTo(new[] { first, second }); + } + + [MemberData(nameof(CustomizationUsageTestData))] + [Theory(DisplayName = "GIVEN valid parameters WHEN customization is used THEN expected values are generated")] + public void GivenValidParameters_WhenCustomizationIsUsed_ThenExpectedValuesAreGenerated( + T item, + params object[] values) + { + // Arrange + var attribute = new PickFromValuesAttribute(values); + var request = new Mock(); + request.SetupGet(x => x.ParameterType) + .Returns(item.GetType()); + IFixture fixture = new Fixture(); + fixture.Customize(attribute.GetCustomization(request.Object)); + + // Act + item = (T)fixture.Create(request.Object, new SpecimenContext(fixture)); + + // Assert + item.Should().NotBeNull().And.Match(x => values.Contains(x)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN byte populated THEN the value from set is generated")] + public void GivenValuesSpecified_WhenBytePopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(1, 5, 20)] byte targetValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(1, 5, 20); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN short populated THEN the value from set is generated")] + public void GivenValuesSpecified_WhenUShortPopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(1, 5, 4)] ushort targetValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(1, 5, 4); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN unsigned long populated THEN the value from set is generated")] + public void GivenValuesSpecified_WhenULongPopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(1, long.MaxValue, ulong.MaxValue)] ulong targetValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(1, long.MaxValue, ulong.MaxValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN signed byte populated THEN the value from set is generated")] + public void GivenValuesSpecified_WhenSBytePopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(sbyte.MinValue, -50, -1)] sbyte targetValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(sbyte.MinValue, -50, -1); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN short populated THEN only decorated parameter has value from specification")] + public void GivenValuesSpecified_WhenShortPopulated_ThenOnlyDecoratedParameterHasValueFromSpecification( + [PickFromValues(short.MinValue, sbyte.MinValue, -1)] short targetValue, + short unrestrictedValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(short.MinValue, sbyte.MinValue, -1); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN integer populated THEN only decorated parameter has value from specification")] + public void GivenValuesSpecified_WhenIntPopulated_ThenOnlyDecoratedParameterHasValueFromSpecification( + [PickFromValues(int.MinValue, short.MinValue, sbyte.MinValue)] int targetValue, + int unrestrictedValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(int.MinValue, short.MinValue, sbyte.MinValue); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN long populated THEN only decorated parameter has value from specification")] + public void GivenValuesSpecified_WhenLongPopulated_ThenOnlyDecoratedParameterHasValueFromSpecification( + [PickFromValues(long.MinValue, int.MinValue, short.MinValue)] long targetValue, + long unrestrictedValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(long.MinValue, int.MinValue, short.MinValue); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN float populated THEN only decorated parameter has value from specification")] + public void GivenValuesSpecified_WhenFloatPopulated_ThenOnlyDecoratedParameterHasValueFromSpecification( + [PickFromValues(float.MinValue, int.MinValue, short.MinValue)] float targetValue, + float unrestrictedValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(float.MinValue, int.MinValue, short.MinValue); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN double populated THEN only decorated parameter has value from specification")] + public void GivenValuesSpecified_WhenDoublePopulated_ThenOnlyDecoratedParameterHasValueFromSpecification( + [PickFromValues(double.MinValue, float.MinValue, int.MinValue, short.MinValue)] double targetValue, + double unrestrictedValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(double.MinValue, float.MinValue, int.MinValue, short.MinValue); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified WHEN decimal populated THEN only decorated parameter has value from specification")] + public void GivenValuesSpecified_WhenDecimalPopulated_ThenOnlyDecoratedParameterHasValueFromSpecification( + [PickFromValues(long.MinValue, int.MinValue, short.MinValue)] decimal targetValue, + decimal unrestrictedValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(long.MinValue, int.MinValue, short.MinValue); + unrestrictedValue.Should().BeGreaterThanOrEqualTo(0); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified for argument WHEN enum populated THEN the value from set is generated")] + public void GivenValuesSpecifiedForArgument_WhenEnumPopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(Numbers.One, Numbers.Five)] Numbers targetValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(Numbers.One, Numbers.Five); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values outside enum specified for argument WHEN enum populated THEN the value from set is generated")] + public void GivenValuesOutsideEnumSpecifiedForArgument_WhenEnumPopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(100, 200)] Numbers targetValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf((Numbers)100, (Numbers)200); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified for argument WHEN flag populated THEN the value from set is generated")] + public void GivenValuesSpecifiedForArgument_WhenFlagPopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(Numbers.One | Numbers.Three, Numbers.Five)] Numbers targetValue) + { + // Arrange + // Act + // Assert + targetValue.Should().BeOneOf(Numbers.One | Numbers.Three, Numbers.Five); + } + + [InlineAutoData(10, 10)] + [InlineAutoData(0, 0)] + [Theory(DisplayName = "GIVEN values specified and inline value outside values WHEN data populated THEN values definition is ignored and inline one is used")] + public void GivenValuesSpecifiedAndInlineValueOutsideValues_WhenDataPopulated_ThenValuesDefinitionIsIgnoredAndInlineOneIsUsed( + [PickFromValues(int.MinValue)] int value, int expectedResult) + { + // Arrange + // Act + // Assert + value.Should().Be(expectedResult).And.NotBe(int.MinValue); + } + + [MemberAutoData(nameof(MemberAutoDataOverValuesTestData))] + [Theory(DisplayName = "GIVEN values specified and member data value outside values WHEN data populated THEN values definition is ignored and member data is used")] + public void GivenValuesSpecifiedAndMemberDataValueOutsideValues_WhenDataPopulated_ThenValuesDefinitionIsIgnoredAndMemberDataIsUsed( + [PickFromValues(int.MinValue)] int value, + int expectedResult, + [PickFromValues(int.MinValue)] int unrestrictedValue) + { + // Arrange + // Act + // Assert + value.Should().Be(expectedResult).And.NotBe(int.MinValue); + unrestrictedValue.Should().Be(int.MinValue); + } + + [AutoData] + [Theory(DisplayName = "GIVEN values specified for collection WHEN unsigned short populated THEN the value from set is generated")] + public void GivenValuesSpecifiedForCollection_WhenEnumPopulated_ThenTheValueFromSetIsGenerated( + [PickFromValues(int.MinValue, (int)short.MinValue, -1)] int[] targetValues, + int[] unrestrictedValues) + { + // Arrange + var supported = new[] { int.MinValue, -1, short.MinValue }; + + // Act + // Assert + targetValues.Should().AllSatisfy(x => supported.Should().Contain(x)); + unrestrictedValues.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0)); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/EnumerableExtensionsTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/EnumerableExtensionsTests.cs new file mode 100644 index 00000000..cf80034c --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/EnumerableExtensionsTests.cs @@ -0,0 +1,109 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Common +{ + using System; + using System.Collections; + using System.Collections.Generic; + + using FluentAssertions; + using global::AutoFixture.Xunit2; + using Objectivity.AutoFixture.XUnit2.Core.Common; + + using Xunit; + + [Collection("EnumerableExtensions")] + [Trait("Category", "Common")] + public class EnumerableExtensionsTests + { + [Fact(DisplayName = "GIVEN uninitialized argument WHEN TryGetEnumerableSingleTypeArgument is invoked THEN exception is thrown")] + public void GivenUninitializedArgument_WhenTryGetEnumerableSingleTypeArgumentIsInvoked_ThenExceptionIsThrown() + { + // Arrange + Type enumerableType = null; + + // Act + // Assert + Assert.Throws(() => enumerableType.TryGetEnumerableSingleTypeArgument(out var itemType)); + } + + [InlineData(typeof(int[]), typeof(int))] + [InlineData(typeof(List), typeof(int))] + [InlineData(typeof(Dictionary), typeof(KeyValuePair))] + [InlineData(typeof(IEnumerable), typeof(int))] + [Theory(DisplayName = "GIVEN valid collection WHEN TryGetEnumerableSingleTypeArgument is invoked THEN collection single type argument returned")] + public void GivenValidCollection_WhenTryGetEnumerableSingleTypeArgumentIsInvoked_ThenCollectionSingleTypeArgumentReturned(Type enumerableType, Type expectedType) + { + // Arrange + // Act + var isSuccessful = enumerableType.TryGetEnumerableSingleTypeArgument(out var itemType); + + // Assert + isSuccessful.Should().BeTrue(); + itemType.Should().Be(expectedType); + } + + [InlineData(typeof(Tuple))] + [InlineData(typeof(IEnumerable))] + [Theory(DisplayName = "GIVEN invalid collection WHEN TryGetEnumerableSingleTypeArgument is invoked THEN no argument returned")] + public void GivenInvalidCollection_WhenTryGetEnumerableSingleTypeArgumentIsInvoked_ThenNoArgumentReturned(Type enumerableType) + { + // Arrange + // Act + var isSuccessful = enumerableType.TryGetEnumerableSingleTypeArgument(out _); + + // Assert + isSuccessful.Should().BeFalse(); + } + + [Fact(DisplayName = "GIVEN generic definition collection WHEN TryGetEnumerableSingleTypeArgument is invoked THEN no argument returned")] + public void GivenGenericDefinitionCollection_WhenTryGetEnumerableSingleTypeArgumentIsInvoked_ThenNoArgumentReturned() + { + // Arrange + var enumerableType = typeof(IEnumerable).GetGenericTypeDefinition(); + + // Act + var isSuccessful = enumerableType.TryGetEnumerableSingleTypeArgument(out _); + + // Assert + isSuccessful.Should().BeFalse(); + } + + [InlineData(null, typeof(int))] + [InlineData(new[] { 1 }, null)] + [Theory(DisplayName = "GIVEN uninitialized argument WHEN ToTypedArray is invoked THEN exception is thrown")] + public void GivenUninitializedArgument_WhenToTypedArrayIsInvoked_ThenExceptionIsThrown(IEnumerable items, Type itemType) + { + // Arrange + // Act + // Assert + Assert.Throws(() => items.ToTypedArray(itemType)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN typed enumerable WHEN ToTypedArray is invoked THEN array is returned")] + public void GivenTypedEnumerable_WhenToTypedArrayIsInvoked_ThenArrayIsReturned(int[] items) + { + // Arrange + var itemType = typeof(int); + + // Act + var array = items.ToTypedArray(itemType); + + // Assert + array.Should().BeEquivalentTo(items).And.Subject.GetType().IsArray.Should().BeTrue(); + } + + [AutoData] + [Theory(DisplayName = "GIVEN enumerable WHEN ToTypedArray is invoked THEN array is returned")] + public void GivenEnumerable_WhenToTypedArrayIsInvoked_ThenArrayIsReturned(BitArray items) + { + // Arrange + var itemType = typeof(bool); + + // Act + var array = items.ToTypedArray(itemType); + + // Assert + array.Should().BeEquivalentTo(items); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/RoundRobinEnumerableTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/RoundRobinEnumerableTests.cs new file mode 100644 index 00000000..7125380b --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Common/RoundRobinEnumerableTests.cs @@ -0,0 +1,57 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Common +{ + using System; + using System.Collections; + using System.Linq; + + using FluentAssertions; + + using global::AutoFixture.Xunit2; + + using Objectivity.AutoFixture.XUnit2.Core.Common; + + using Xunit; + + [Collection("RoundRobinEnumerable")] + [Trait("Category", "Common")] + public class RoundRobinEnumerableTests + { + [Fact(DisplayName = "GIVEN uninitialized argument WHEN constructor is invoked THEN exception is thrown")] + public void GivenUninitializedArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new RoundRobinEnumerable(null)); + } + + [Fact(DisplayName = "GIVEN no arguments WHEN constructor is invoked THEN exception is thrown")] + public void GivenNoArguments_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new RoundRobinEnumerable()); + } + + [AutoData] + [Theory(DisplayName = "GIVEN round robin with values WHEN enumerating twice THEN ordered values enumerated twice")] + public void GivenRoundRobinWithValues_WhenEnumeratingTwice_ThenOrderedValuesEnumeratedTwice( + int[] values) + { + // Arrange + var enumerator = ((IEnumerable)new RoundRobinEnumerable(values)).GetEnumerator(); + var duplicatedValues = values.Concat(values.ToArray()).ToArray(); + + // Act + var items = duplicatedValues.Select(x => + { + enumerator.MoveNext(); + return x.Equals(enumerator.Current); + }).ToArray(); + + // Assert + items.Should().AllSatisfy(x => x.Should().BeTrue()).And.HaveCount(duplicatedValues.Length); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Comparers/CustomizeAttributeComparerTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Comparers/CustomizeAttributeComparerTests.cs index 71eb481d..ed36dde9 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Comparers/CustomizeAttributeComparerTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Comparers/CustomizeAttributeComparerTests.cs @@ -28,8 +28,8 @@ public class CustomizeAttributeComparerTests new object[] { CustomizeAttribute, FrozenAttribute, -1 }, }; - [Theory(DisplayName = "GIVEN both attributes WHEN Compare is invoked THEN expected result returned")] [MemberData(nameof(TestData))] + [Theory(DisplayName = "GIVEN both attributes WHEN Compare is invoked THEN expected result returned")] public void GivenBothNonFrozenAttributes_WhenCompareIsInvoked_ThenBothEquals( IParameterCustomizationSource x, IParameterCustomizationSource y, diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/AutoDataCommonCustomizationTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/AutoDataCommonCustomizationTests.cs index 2b26236c..df2ba2d0 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/AutoDataCommonCustomizationTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/AutoDataCommonCustomizationTests.cs @@ -10,8 +10,8 @@ [Trait("Category", "Customizations")] public class AutoDataCommonCustomizationTests { - [Theory(DisplayName = "GIVEN existing customization with ignoring virtual members WHEN Customize is invoked THEN fixture is appropriately customized")] [AutoData] + [Theory(DisplayName = "GIVEN existing customization with ignoring virtual members WHEN Customize is invoked THEN fixture is appropriately customized")] public void GivenExistingCustomizationForFixture_WhenCustomizeIsInvoked_ThenFixtureIsAppropriatelyCustomized(Fixture fixture) { // Arrange @@ -27,8 +27,8 @@ public void GivenExistingCustomizationForFixture_WhenCustomizeIsInvoked_ThenFixt fixture.ShouldIgnoreVirtualMembers(); } - [Theory(DisplayName = "GIVEN existing customization without ignoring virtual members WHEN Customize is invoked THEN fixture is appropriately customized")] [AutoData] + [Theory(DisplayName = "GIVEN existing customization without ignoring virtual members WHEN Customize is invoked THEN fixture is appropriately customized")] public void GivenExistingCustomizationWithoutIgnoringVirtualMembers_WhenCustomizeIsInvoked_ThenFixtureIsAppropriatelyCustomized(Fixture fixture) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/DoNotThrowOnRecursionCustomizationTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/DoNotThrowOnRecursionCustomizationTests.cs index d674fc70..41976a15 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/DoNotThrowOnRecursionCustomizationTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/DoNotThrowOnRecursionCustomizationTests.cs @@ -10,8 +10,8 @@ [Trait("Category", "Customizations")] public class DoNotThrowOnRecursionCustomizationTests { - [Theory(DisplayName = "GIVEN existing customization for fixture WHEN Customize is invoked THEN fixture should not throw on recursion")] [AutoData] + [Theory(DisplayName = "GIVEN existing customization for fixture WHEN Customize is invoked THEN fixture should not throw on recursion")] public void GivenExistingCustomizationForFixture_WhenCustomizeIsInvoked_ThenFixtureShouldNotThrowOnRecursion(Fixture fixture, DoNotThrowOnRecursionCustomization customization) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/IgnoreVirtualMembersCustomizationTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/IgnoreVirtualMembersCustomizationTests.cs index 40360174..20c1e87d 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/IgnoreVirtualMembersCustomizationTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/IgnoreVirtualMembersCustomizationTests.cs @@ -14,8 +14,8 @@ [Trait("Category", "Customizations")] public class IgnoreVirtualMembersCustomizationTests { - [Theory(DisplayName = "GIVEN existing customization to ignore virtual members for fixture WHEN Customize is invoked THEN fixture should not create virtual members")] [AutoData] + [Theory(DisplayName = "GIVEN existing customization to ignore virtual members for fixture WHEN Customize is invoked THEN fixture should not create virtual members")] public void GivenExistingCustomizationToIgnoreVirtualMembersForFixture_WhenCustomizeIsInvoked_ThenFixtureShouldNotCreateVirtualMembers( Fixture fixture, [Modest] IgnoreVirtualMembersCustomization customization) @@ -28,8 +28,8 @@ public void GivenExistingCustomizationToIgnoreVirtualMembersForFixture_WhenCusto fixture.ShouldIgnoreVirtualMembers(); } - [Theory(DisplayName = "GIVEN existing customization to ignore virtual members for fixture WHEN Customize is invoked THEN fixture should not create virtual members")] [AutoData] + [Theory(DisplayName = "GIVEN existing customization to ignore virtual members for fixture WHEN Customize is invoked THEN fixture should not create virtual members")] public void GivenExistingCustomizationToIgnoreVirtualMembersWithTypeForFixture_WhenCustomizeIsInvoked_ThenFixtureShouldNotCreateVirtualMembers( Fixture fixture, [Frozen] Type reflectedType, @@ -54,8 +54,8 @@ public void GivenDefaultConstructor_WhenInvoked_ThenReflectedTypeShouldBeNull() customization.ReflectedType.Should().BeNull(); } - [Theory(DisplayName = "GIVEN existing type WHEN constructor with parameter is invoked THEN that type should be reflected")] [AutoData] + [Theory(DisplayName = "GIVEN existing type WHEN constructor with parameter is invoked THEN that type should be reflected")] public void GivenExistingType_WhenConstructorWithParameterIsInvoked_ThenThatTypeShouldBeReflected(Type reflectedType) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/OmitOnRecursionCustomizationTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/OmitOnRecursionCustomizationTests.cs index e4c191dd..1d23725e 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/OmitOnRecursionCustomizationTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Customizations/OmitOnRecursionCustomizationTests.cs @@ -10,8 +10,8 @@ [Trait("Category", "Customizations")] public class OmitOnRecursionCustomizationTests { - [Theory(DisplayName = "GIVEN existing customization for fixture WHEN Customize is invoked THEN fixture should omit recursion")] [AutoData] + [Theory(DisplayName = "GIVEN existing customization for fixture WHEN Customize is invoked THEN fixture should omit recursion")] public void GivenExistingCustomizationForFixture_WhenCustomizeIsInvoked_ThenFixtureShouldOmitRecursion(Fixture fixture, OmitOnRecursionCustomization customization) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/MemberData/MemberAutoDataItemConverterTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/MemberData/MemberAutoDataItemConverterTests.cs index 26f53cd6..a9c04619 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/MemberData/MemberAutoDataItemConverterTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/MemberData/MemberAutoDataItemConverterTests.cs @@ -94,8 +94,8 @@ public void GivenUninitializedItem_WhenConvertInvoked_ThenNullReturned() data.Should().BeNull(); } - [Theory(DisplayName = "GIVEN item of unexpected type WHEN Convert is invoked THEN exception is thrown")] [MemberData(nameof(MemberTypeTestData))] + [Theory(DisplayName = "GIVEN item of unexpected type WHEN Convert is invoked THEN exception is thrown")] public void GivenItemOfUnexpectedType_WhenConvertIsInvoked_ThenExceptionIsThrown( Type memberType, string expectedTypeName) diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/AutoDataAttributeProviderTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/AutoDataAttributeProviderTests.cs index 44f26ef2..1ec8cc99 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/AutoDataAttributeProviderTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/AutoDataAttributeProviderTests.cs @@ -15,9 +15,9 @@ [Trait("Category", "Providers")] public class AutoDataAttributeProviderTests { - [Theory(DisplayName = "GIVEN initialized fixture WHEN GetAttribute is invoked THEN attribute with specified fixture is returned")] - [AutoData] [SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Assertion checks it earlier and throws exception.")] + [AutoData] + [Theory(DisplayName = "GIVEN initialized fixture WHEN GetAttribute is invoked THEN attribute with specified fixture is returned")] public void GivenInitializedFixture_WhenGetAttributeIsInvoked_ThenAttributeWithSpecifiedFixtureIsReturned(Fixture fixture) { // Arrange diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/InlineAutoDataAttributeProviderTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/InlineAutoDataAttributeProviderTests.cs index 8eec9e14..8d05241e 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/InlineAutoDataAttributeProviderTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/InlineAutoDataAttributeProviderTests.cs @@ -15,9 +15,9 @@ [Trait("Category", "Providers")] public class InlineAutoDataAttributeProviderTests { - [Theory(DisplayName = "GIVEN initialized fixture WHEN GetAttribute is invoked THEN attribute with specified fixture is returned")] - [AutoData] [SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Assertion checks it earlier and throws exception.")] + [AutoData] + [Theory(DisplayName = "GIVEN initialized fixture WHEN GetAttribute is invoked THEN attribute with specified fixture is returned")] public void GivenInitializedFixture_WhenGetAttributeIsInvoked_ThenAttributeWithSpecifiedFixtureIsReturned( Fixture fixture, object[] inlineValues) diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Requests/ValuesRequestTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Requests/ValuesRequestTests.cs new file mode 100644 index 00000000..6ce13611 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/Requests/ValuesRequestTests.cs @@ -0,0 +1,216 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Requests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + + using FluentAssertions; + using global::AutoFixture.Xunit2; + using Objectivity.AutoFixture.XUnit2.Core.Requests; + + using Xunit; + + [Collection("ValuesRequest")] + [Trait("Category", "Requests")] + public class ValuesRequestTests + { + public static IEnumerable ComparisonTestData { get; } = new[] + { + new object[] { typeof(int), new[] { 1 }, typeof(int), new[] { 1 }, true }, + new object[] { typeof(int), new[] { 1, 2, 3 }, typeof(int), new[] { 3, 2, 1 }, true }, + new object[] { typeof(int?), new[] { 1, (int?)null }, typeof(int?), new[] { 1, null, (int?)null }, true }, + new object[] { typeof(int), new[] { 1 }, typeof(long), new[] { 1 }, false }, + new object[] { typeof(int), new[] { 1 }, typeof(int), new[] { 2 }, false }, + new object[] { typeof(int), new[] { 1, 2 }, typeof(int), new[] { 2 }, false }, + }; + + [Fact(DisplayName = "GIVEN uninitialized type argument WHEN constructor is invoked THEN exception is thrown")] + public void GivenUninitializedTypeArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + Type type = null; + object[] values = null; + + // Act + // Assert + Assert.Throws(() => new FixedValuesRequest(type, values)); + } + + [Fact(DisplayName = "GIVEN uninitialized values argument WHEN constructor is invoked THEN exception is thrown")] + public void GivenUninitializedValuesArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var type = typeof(int); + object[] values = null; + + // Act + // Assert + Assert.Throws(() => new FixedValuesRequest(type, values)); + } + + [Fact(DisplayName = "GIVEN empty argument WHEN constructor is invoked THEN exception is thrown")] + public void GivenEmptyArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var type = typeof(int); + var values = Array.Empty(); + + // Act + // Assert + Assert.Throws(() => new FixedValuesRequest(type, values)); + } + + [InlineData(typeof(int), 2)] + [InlineData(1, typeof(int))] + [Theory(DisplayName = "GIVEN incomparable argument WHEN constructor is invoked THEN parameters are properly assigned")] + public void GivenIncomparableArgument_WhenConstructorIsInvoked_ThenParametersAreProperlyAssigned( + object first, + object second) + { + // Arrange + var type = typeof(int); + + // Act + var attribute = new FixedValuesRequest(type, first, second); + + // Assert + attribute.Values.Should().HaveCount(2).And.BeEquivalentTo(new[] { first, second }); + } + + [Fact(DisplayName = "GIVEN valid arguments WHEN ToString is invoked THEN text conteins necessary information")] + public void GivenValidArguments_WhenToStringIsInvoked_ThenTextConteinsNecessaryInformation() + { + // Arrange + var type = typeof(float); + const int first = int.MaxValue; + long? second = long.MinValue; + byte? third = null; + var attribute = new FixedValuesRequest(type, first, second, third); + + // Act + var text = attribute.ToString(); + + // Assert + text.Should().Contain(nameof(FixedValuesRequest)) + .And.Contain(type.Name) + .And.Contain("[Int32]") + .And.Contain(first.ToString(CultureInfo.InvariantCulture)) + .And.Contain("[Int64]") + .And.Contain(second.Value.ToString(CultureInfo.InvariantCulture)) + .And.Contain("[Object]") + .And.Contain("null"); + } + + [MemberData(nameof(ComparisonTestData))] + [Theory(DisplayName = "GIVEN two requests WHEN Equals is invoked THEN expected value is returned")] + public void GivenTwoRequests_WhenEqualsIsInvoked_ThenExpectedValueIsReturned( + Type typeA, + IEnumerable valuesA, + Type typeB, + IEnumerable valuesB, + bool expectedResult) + { + // Arrange + var a = new FixedValuesRequest(typeA, valuesA.Cast().ToArray()); + var b = new FixedValuesRequest(typeB, valuesB.Cast().ToArray()); + + // Act + var result = Equals(a, b); + + // Assert + result.Should().Be(expectedResult); + } + + [MemberData(nameof(ComparisonTestData))] + [Theory(DisplayName = "GIVEN two requests WHEN GetHashCode is invoked THEN expected value is returned")] + public void GivenTwoRequests_WhenGetHashCodeIsInvoked_ThenExpectedValueIsReturned( + Type typeA, + IEnumerable valuesA, + Type typeB, + IEnumerable valuesB, + bool expectedResult) + { + // Arrange + var a = new FixedValuesRequest(typeA, valuesA.Cast().ToArray()); + var b = new FixedValuesRequest(typeB, valuesB.Cast().ToArray()); + + // Act + var hashA = a.GetHashCode(); + var hashB = b.GetHashCode(); + var result = hashA == hashB; + + // Assert + result.Should().Be(expectedResult, $"Hash codes are different. First: {a}, HashCode: {hashA.ToString(CultureInfo.InvariantCulture)}, Second:{b}, HashCode: {hashB.ToString(CultureInfo.InvariantCulture)}"); + } + + [Fact(DisplayName = "GIVEN uninitialized request WHEN Equals is invoked THEN False is returned")] + [SuppressMessage("Maintainability", "CA1508:Avoid dead conditional code", Justification = "Test the logic")] + public void GivenUninitializedRequest_WhenEqualsIsInvoked_ThenFalseIsReturned() + { + // Arrange + var initialized = new FixedValuesRequest(typeof(int), 1); + FixedValuesRequest uninitialized = null; + + // Act + var result = initialized.Equals(uninitialized); + + // Assert + result.Should().Be(false); + } + + [Fact(DisplayName = "GIVEN different type of object WHEN Equals is invoked THEN False is returned")] + public void GivenDifferentTypeOfObject_WhenEqualsIsInvoked_ThenFalseIsReturned() + { + // Arrange + var request = new FixedValuesRequest(typeof(int), 1); + var differentObject = typeof(int); + + // Act + var result = request.Equals(differentObject); + + // Assert + result.Should().Be(false); + } + + [AutoData] + [Theory(DisplayName = "GIVEN different type of ValuesRequest WHEN Equals is invoked THEN False is returned")] + public void GivenDifferentTypeOfValuesRequest_WhenEqualsIsInvoked_ThenFalseIsReturned( + int value) + { + // Arrange + var type = value.GetType(); + var fixedRequest = new FixedValuesRequest(type, value); + var exceptRequest = new ExceptValuesRequest(type, value); + + // Act + var result = fixedRequest.Equals(exceptRequest); + var text = fixedRequest.ToString(); + + // Assert + result.Should().Be(false); + text.Should().NotBeNull(); + } + + [AutoData] + [Theory(DisplayName = "GIVEN different type of ValuesRequest WHEN GetHashCode is invoked THEN False is returned")] + public void GivenDifferentTypeOfValuesRequest_WhenGetHashCodeIsInvoked_ThenFalseIsReturned( + int value) + { + // Arrange + var type = value.GetType(); + var fixedRequest = new FixedValuesRequest(type, value); + var exceptRequest = new ExceptValuesRequest(type, value); + + // Act + var hashA = fixedRequest.GetHashCode(); + var hashB = exceptRequest.GetHashCode(); + var result = hashA == hashB; + + // Assert + result.Should().Be(false); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/IgnoreVirtualMembersSpecimenBuilderTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/IgnoreVirtualMembersSpecimenBuilderTests.cs index c495cde5..3da797a2 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/IgnoreVirtualMembersSpecimenBuilderTests.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/IgnoreVirtualMembersSpecimenBuilderTests.cs @@ -34,8 +34,8 @@ public void GivenDefaultConstructor_WhenInvoked_ThenReflectedTypeShouldBeNull() builder.ReflectedType.Should().BeNull(); } - [Theory(DisplayName = "GIVEN existing type WHEN constructor with parameter is invoked THEN that type should be reflected")] [AutoData] + [Theory(DisplayName = "GIVEN existing type WHEN constructor with parameter is invoked THEN that type should be reflected")] public void GivenExistingType_WhenConstructorWithParameterIsInvoked_ThenThatTypeShouldBeReflected(Type reflectedType) { // Arrange @@ -46,8 +46,8 @@ public void GivenExistingType_WhenConstructorWithParameterIsInvoked_ThenThatType builder.ReflectedType.Should().BeSameAs(reflectedType); } - [Theory(DisplayName = "GIVEN uninitialized request WHEN Create is invoked THEN NoSpecimen is returned")] [AutoData] + [Theory(DisplayName = "GIVEN uninitialized request WHEN Create is invoked THEN NoSpecimen is returned")] public void GivenUninitializedRequest_WhenCreateInvoked_ThenNoSpecimenInstance( [Modest] IgnoreVirtualMembersSpecimenBuilder builder) { @@ -59,8 +59,8 @@ public void GivenUninitializedRequest_WhenCreateInvoked_ThenNoSpecimenInstance( specimen.Should().BeOfType(); } - [Theory(DisplayName = "GIVEN not PropertyInfo request WHEN Create is invoked THEN NoSpecimen is returned")] [AutoData] + [Theory(DisplayName = "GIVEN not PropertyInfo request WHEN Create is invoked THEN NoSpecimen is returned")] public void GivenNotPropertyInfoRequest_WhenCreateInvoked_ThenNoSpecimenInstance( [Modest] IgnoreVirtualMembersSpecimenBuilder builder) { @@ -72,8 +72,8 @@ public void GivenNotPropertyInfoRequest_WhenCreateInvoked_ThenNoSpecimenInstance specimen.Should().BeOfType(); } - [Theory(DisplayName = "GIVEN not virtual PropertyInfo request WHEN Create is invoked THEN NoSpecimen is returned")] [AutoData] + [Theory(DisplayName = "GIVEN not virtual PropertyInfo request WHEN Create is invoked THEN NoSpecimen is returned")] public void GivenNotVirtualPropertyInfoRequest_WhenCreateInvoked_ThenNoSpecimenInstance( [Modest] IgnoreVirtualMembersSpecimenBuilder builder) { @@ -87,8 +87,8 @@ public void GivenNotVirtualPropertyInfoRequest_WhenCreateInvoked_ThenNoSpecimenI specimen.Should().BeOfType(); } - [Theory(DisplayName = "GIVEN virtual PropertyInfo request but hosted in different type WHEN Create is invoked THEN NoSpecimen is returned")] [AutoData] + [Theory(DisplayName = "GIVEN virtual PropertyInfo request but hosted in different type WHEN Create is invoked THEN NoSpecimen is returned")] public void GivenVirtualPropertyInfoRequestHostedInDifferentType_WhenCreateInvoked_ThenNoSpecimenInstance( [Frozen] Type reflectedType, [Greedy] IgnoreVirtualMembersSpecimenBuilder builder) @@ -104,8 +104,8 @@ public void GivenVirtualPropertyInfoRequestHostedInDifferentType_WhenCreateInvok specimen.Should().BeOfType(); } - [Theory(DisplayName = "GIVEN virtual PropertyInfo request WHEN Create is invoked THEN OmitSpecimen is returned")] [AutoData] + [Theory(DisplayName = "GIVEN virtual PropertyInfo request WHEN Create is invoked THEN OmitSpecimen is returned")] public void GivenVirtualPropertyInfoRequest_WhenCreateInvoked_ThenOmitSpecimenInstance( [Modest] IgnoreVirtualMembersSpecimenBuilder builder) { diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomExceptValuesGeneratorTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomExceptValuesGeneratorTests.cs new file mode 100644 index 00000000..c8f24e56 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomExceptValuesGeneratorTests.cs @@ -0,0 +1,74 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.SpecimenBuilders +{ + using System; + + using FluentAssertions; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + + using Moq; + + using Objectivity.AutoFixture.XUnit2.Core.Requests; + using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders; + + using Xunit; + + [Collection("RandomExceptValuesGenerator")] + [Trait("Category", "SpecimenBuilders")] + public class RandomExceptValuesGeneratorTests + { + [Fact(DisplayName = "GIVEN uninitialized context WHEN Create is invoked THEN exception is thrown")] + public void GivenUninitializedContext_WhenCreateIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var builder = new RandomExceptValuesGenerator(); + + // Act + // Assert + Assert.Throws(() => builder.Create(new object(), null)); + } + + [Fact(DisplayName = "GIVEN uninitialized request WHEN Create is invoked THEN exception is thrown")] + public void GivenUninitializedRequest_WhenCreateIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var builder = new RandomExceptValuesGenerator(); + var context = new Mock(); + + // Act + // Assert + Assert.Throws(() => builder.Create(null, context.Object)); + } + + [Fact(DisplayName = "GIVEN excluded value and context which resolves to the same value WHEN Create is invoked THEN exception is thrown")] + public void GivenExcludedValueAndContextWhichResolvesToTheSameValue_WhenCreateIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var builder = new RandomExceptValuesGenerator(); + var context = new Mock(); + const int duplicateValue = 5; + context.Setup(x => x.Resolve(It.IsNotNull())).Returns(duplicateValue); + var request = new ExceptValuesRequest(duplicateValue.GetType(), duplicateValue); + + // Act + // Assert + Assert.Throws(() => builder.Create(request, context.Object)); + } + + [Fact(DisplayName = "GIVEN unsupported request WHEN Create is invoked THEN NoSpecimen is returned")] + public void GivenUnsupportedRequest_WhenCreateIsInvoked_ThenNoSpecimenIsReturned() + { + // Arrange + var builder = new RandomExceptValuesGenerator(); + var context = new Mock(); + var request = typeof(string); + + // Act + var result = builder.Create(request, context.Object); + + // Assert + result.Should().NotBeNull().And.BeOfType(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomFixedValuesGeneratorTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomFixedValuesGeneratorTests.cs new file mode 100644 index 00000000..7d0fe8e5 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RandomFixedValuesGeneratorTests.cs @@ -0,0 +1,116 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.SpecimenBuilders +{ + using System; + using System.Linq; + + using FluentAssertions; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using global::AutoFixture.Xunit2; + using Moq; + + using Objectivity.AutoFixture.XUnit2.Core.Requests; + using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders; + + using Xunit; + + [Collection("RandomFixedValuesGenerator")] + [Trait("Category", "SpecimenBuilders")] + public class RandomFixedValuesGeneratorTests + { + [Fact(DisplayName = "GIVEN uninitialized context WHEN Create is invoked THEN exception is thrown")] + public void GivenUninitializedContext_WhenCreateIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var builder = new RandomFixedValuesGenerator(); + + // Act + // Assert + Assert.Throws(() => builder.Create(new object(), null)); + } + + [Fact(DisplayName = "GIVEN uninitialized request WHEN Create is invoked THEN exception is thrown")] + public void GivenUninitializedRequest_WhenCreateIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var builder = new RandomFixedValuesGenerator(); + var context = new Mock(); + + // Act + // Assert + Assert.Throws(() => builder.Create(null, context.Object)); + } + + [Fact(DisplayName = "GIVEN unsupported request WHEN Create is invoked THEN NoSpecimen is returned")] + public void GivenUnsupportedRequest_WhenCreateIsInvoked_ThenNoSpecimenIsReturned() + { + // Arrange + var builder = new RandomFixedValuesGenerator(); + var context = new Mock(); + var request = typeof(string); + + // Act + var result = builder.Create(request, context.Object); + + // Assert + result.Should().NotBeNull().And.BeOfType(); + } + + [Fact(DisplayName = "GIVEN request with many values WHEN Create is invoked many times THEN only defined values are returned")] + public void GivenRequestWithManyValues_WhenCreateIsInvokedManyTimes_ThenOnlyDefinedValuesAreReturned() + { + // Arrange + var builder = new RandomFixedValuesGenerator(); + var context = new Mock(); + var request = new FixedValuesRequest(typeof(int), 1, 2); + + // Act + var result = Enumerable.Range(0, 10).Select((_) => (int)builder.Create(request, context.Object)); + + // Assert + result.Should().NotBeNull() + .And.HaveCount(10) + .And.AllSatisfy(x => x.Should().BeOneOf(1, 2)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN request with single value WHEN Create is invoked many times THEN only repeated defined value is returned")] + internal void GivenRequestWithSingleValue_WhenCreateIsInvokedManyTimes_ThenOnlyRepeatedDefinedValueIsReturned( + RandomFixedValuesGenerator builder, + IFixture fixture) + { + // Arrange + var context = new Mock(); + var expectedValue = fixture.Create(); + var request = new FixedValuesRequest(typeof(int), expectedValue); + + // Act + var result = Enumerable.Range(0, 10).Select((_) => (int)builder.Create(request, context.Object)); + + // Assert + result.Should().NotBeNull() + .And.HaveCount(10) + .And.AllSatisfy(x => x.Should().Be(expectedValue)); + } + + [AutoData] + [Theory(DisplayName = "GIVEN many requests WHEN Create is invoked per each request THEN different defined value are returned")] + internal void GivenManyRequests_WhenCreateIsInvokedPerEachRequest_ThenDifferentDefinedValueAreReturned( + RandomFixedValuesGenerator builder) + { + // Arrange + var context = new Mock(); + var setup = new[] { 1, 2 }.ToDictionary( + x => x, + x => new FixedValuesRequest(typeof(int), x)); + + // Act + var result = setup.Select(x => (int)builder.Create(x.Value, context.Object)).ToArray(); + + // Assert + result.First().Should().Be(setup.First().Key); + result.Last().Should().Be(setup.Last().Key); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RequestFactoryRelayTests.cs b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RequestFactoryRelayTests.cs new file mode 100644 index 00000000..14612069 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core.Tests/SpecimenBuilders/RequestFactoryRelayTests.cs @@ -0,0 +1,199 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Tests.SpecimenBuilders +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Reflection; + + using FluentAssertions; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using global::AutoFixture.Xunit2; + + using Moq; + + using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders; + + using Xunit; + + [Collection("RequestFactoryRelay")] + [Trait("Category", "SpecimenBuilders")] + public class RequestFactoryRelayTests + { + [Fact(DisplayName = "GIVEN uninitialized argument WHEN constructor is invoked THEN exception is thrown")] + public void GivenUninitializedArgument_WhenConstructorIsInvoked_ThenExceptionIsThrown() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new RequestFactoryRelay(null)); + } + + [Fact(DisplayName = "GIVEN empty argument WHEN Create is invoked THEN exception is thrown")] + public void GivenEmptyArgument_WhenCreateIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var factory = new Mock>(); + var builder = new RequestFactoryRelay(factory.Object); + + // Act + // Assert + Assert.Throws(() => builder.Create(new object(), null)); + factory.VerifyNoOtherCalls(); + } + + [Fact(DisplayName = "GIVEN uninitialized request WHEN Create is invoked THEN exception is thrown")] + public void GivenUninitializedRequest_WhenCreateIsInvoked_ThenExceptionIsThrown() + { + // Arrange + var factory = new Mock>(); + var builder = new RequestFactoryRelay(factory.Object); + var context = new Mock(); + + // Act + // Assert + Assert.Throws(() => builder.Create(null, context.Object)); + } + + [Fact(DisplayName = "GIVEN unsupported request type WHEN create is invoked THEN NoSpecimen is returned")] + public void GivenUnsupportedRequestType_WhenCreateIsInvoked_ThenNoSpecimenIsReturned() + { + // Arrange + var factory = new Mock>(); + var builder = new RequestFactoryRelay(factory.Object); + var context = new Mock(); + var request = new object(); + + // Act + var result = builder.Create(request, context.Object); + + // Assert + result.Should().BeOfType(); + factory.VerifyNoOtherCalls(); + } + + [InlineAutoData(typeof(int))] + [InlineAutoData(typeof(int[]))] + [Theory(DisplayName = "GIVEN empty request WHEN create is invoked THEN NoSpecimen is returned")] + public void GivenEmptyRequest_WhenCreateIsInvoked_ThenNoSpecimenIsReturned( + Type requestType) + { + // Arrange + var factory = new Mock>(); + factory.Setup(x => x(It.IsAny())).Returns(null); + var builder = new RequestFactoryRelay(factory.Object); + var context = new Mock(); + var request = new Mock(); + request.SetupGet(x => x.ParameterType) + .Returns(requestType); + + // Act + var result = builder.Create(request.Object, context.Object); + + // Assert + result.Should().BeOfType(); + factory.Verify(x => x(It.IsAny()), Times.Once); + factory.VerifyNoOtherCalls(); + context.VerifyNoOtherCalls(); + } + + [InlineAutoData(typeof(int), typeof(int))] + [InlineAutoData(typeof(int?), typeof(int))] + [Theory(DisplayName = "GIVEN type request WHEN create is invoked THEN expected type is returned")] + public void GivenTypeRequest_WhenCreateIsInvoked_ThenExpectedTypeIsReturned( + Type requestType, + Type expectedType, + IFixture fixture) + { + // Arrange + var factory = new Mock>(); + factory.Setup(x => x(It.IsAny())) + .Returns(x => x); + var builder = new RequestFactoryRelay(factory.Object); + var context = new Mock(); + context.Setup(x => x.Resolve(It.IsAny())) + .Returns((request) => new SpecimenContext(fixture).Resolve(request)); + var request = new Mock(); + request.SetupGet(x => x.ParameterType) + .Returns(requestType); + + // Act + var result = builder.Create(request.Object, context.Object); + + // Assert + result.Should().NotBeNull() + .And.BeOfType(expectedType); + factory.Verify(x => x(It.IsAny()), Times.Once); + factory.VerifyNoOtherCalls(); + context.Verify(x => x.Resolve(It.IsAny()), Times.Once); + context.VerifyNoOtherCalls(); + } + + [AutoData] + [Theory(DisplayName = "GIVEN valid enumerable type request WHEN create is invoked resulting in unsupported type THEN NoSpecimen is returned")] + public void GivenValidEnumerableTypeRequest_WhenCreateIsInvokedResultingInUnsupportedType_ThenNoSpecimenIsReturned( + IFixture fixture) + { + // Arrange + var values = fixture.CreateMany(1).ToArray(); + var value = values.First(); + var factory = new Mock>(); + factory.Setup(x => x(It.IsAny())).Returns(value); + var builder = new RequestFactoryRelay(factory.Object); + var context = new Mock(); + context.Setup(x => x.Resolve(It.IsAny())).Returns(value); + var request = new Mock(); + request.SetupGet(x => x.ParameterType).Returns(values.GetType()); + + // Act + var result = builder.Create(request.Object, context.Object); + + // Assert + result.Should().BeOfType(); + factory.Verify(x => x(It.IsAny()), Times.Once); + factory.VerifyNoOtherCalls(); + context.Verify(x => x.Resolve(It.IsAny()), Times.Once); + context.VerifyNoOtherCalls(); + } + + [InlineAutoData(typeof(int[]), typeof(int[]))] + [InlineAutoData(typeof(int?[]), typeof(int?[]))] + [InlineAutoData(typeof(IEnumerable), typeof(int[]))] + [InlineAutoData(typeof(IEnumerable), typeof(int?[]))] + [InlineAutoData(typeof(Collection), typeof(Collection))] + [InlineAutoData(typeof(Collection), typeof(Collection))] + [Theory(DisplayName = "GIVEN valid enumerable type request WHEN create is invoked THEN expected collection is returned")] + public void GivenValidEnumerableTypeRequest_WhenCreateIsInvoked_ThenExpectedCollectionIsReturned( + Type requestType, + Type expectedType, + IFixture fixture) + { + // Arrange + var factory = new Mock>(); + factory.Setup(x => x(It.IsAny())) + .Returns(x => x); + var builder = new RequestFactoryRelay(factory.Object); + var context = new Mock(); + context.Setup(x => x.Resolve(It.IsAny())) + .Returns((request) => new SpecimenContext(fixture).Resolve(request)); + var request = new Mock(); + request.SetupGet(x => x.ParameterType) + .Returns(requestType); + + // Act + var result = builder.Create(request.Object, context.Object); + + // Assert + result.Should().NotBeNull() + .And.BeOfType(expectedType) + .And.Subject.As().Cast().Should().NotBeEmpty(); + factory.Verify(x => x(It.IsAny()), Times.Once); + factory.VerifyNoOtherCalls(); + context.Verify(x => x.Resolve(It.IsAny()), Times.Once); + context.VerifyNoOtherCalls(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute.cs index ee69b6d8..05d438bf 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute.cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute.cs @@ -7,9 +7,10 @@ using global::AutoFixture; using global::AutoFixture.Xunit2; + using Objectivity.AutoFixture.XUnit2.Core.Common; - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] [SuppressMessage("Performance", "CA1813:Avoid unsealed attributes", Justification = "This attribute should be extendable by inheritance.")] public class CustomizeWithAttribute : CustomizeAttribute { diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute[T].cs b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute[T].cs index 2302ca4a..d88cc5d0 100644 --- a/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute[T].cs +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/CustomizeWithAttribute[T].cs @@ -5,7 +5,7 @@ using global::AutoFixture; - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] [SuppressMessage("Performance", "CA1813:Avoid unsealed attributes", Justification = "This attribute should be extendable by inheritance.")] public class CustomizeWithAttribute : CustomizeWithAttribute where T : ICustomization diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/ExceptAttribute.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/ExceptAttribute.cs new file mode 100644 index 00000000..4733cc7c --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/ExceptAttribute.cs @@ -0,0 +1,45 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Attributes +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using global::AutoFixture.Xunit2; + + using Objectivity.AutoFixture.XUnit2.Core.Common; + using Objectivity.AutoFixture.XUnit2.Core.Requests; + using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders; + + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class ExceptAttribute : CustomizeAttribute + { + private readonly HashSet inputValues; + private readonly Lazy> readonlyValues; + + public ExceptAttribute(params object[] values) + { + this.inputValues = new HashSet(values.NotNull(nameof(values))); + if (this.inputValues.Count == 0) + { + throw new ArgumentException("At least one value is expected to be specified.", nameof(values)); + } + + this.readonlyValues = new Lazy>(() => Array.AsReadOnly(this.inputValues.ToArray())); + } + + public IReadOnlyCollection Values => this.readonlyValues.Value; + + public override ICustomization GetCustomization(ParameterInfo parameter) + { + return new CompositeSpecimenBuilder( + new FilteringSpecimenBuilder( + new RequestFactoryRelay((type) => new ExceptValuesRequest(type, this.inputValues.ToArray())), + new EqualRequestSpecification(parameter)), + new RandomExceptValuesGenerator()) + .ToCustomization(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromRangeAttribute.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromRangeAttribute.cs new file mode 100644 index 00000000..c34b5921 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromRangeAttribute.cs @@ -0,0 +1,68 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Attributes +{ + using System; + using System.Reflection; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using global::AutoFixture.Xunit2; + + using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders; + + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class PickFromRangeAttribute : CustomizeAttribute + { + public PickFromRangeAttribute(int minimum, int maximum) + : this((object)minimum, maximum) + { + } + + public PickFromRangeAttribute(uint minimum, uint maximum) + : this((object)minimum, maximum) + { + } + + public PickFromRangeAttribute(long minimum, long maximum) + : this((object)minimum, maximum) + { + } + + public PickFromRangeAttribute(ulong minimum, ulong maximum) + : this((object)minimum, maximum) + { + } + + public PickFromRangeAttribute(double minimum, double maximum) + : this((object)minimum, maximum) + { + } + + public PickFromRangeAttribute(float minimum, float maximum) + : this((object)minimum, maximum) + { + } + + private PickFromRangeAttribute(object minimum, object maximum) + { + if (((IComparable)minimum).CompareTo((IComparable)maximum) > 0) + { + throw new ArgumentOutOfRangeException(nameof(minimum), $"Parameter {nameof(minimum)} must be lower or equal to parameter {nameof(maximum)}."); + } + + this.Minimum = minimum; + this.Maximum = maximum; + } + + public object Minimum { get; } + + public object Maximum { get; } + + public override ICustomization GetCustomization(ParameterInfo parameter) + { + return new FilteringSpecimenBuilder( + new RequestFactoryRelay((type) => new RangedNumberRequest(type, this.Minimum, this.Maximum)), + new EqualRequestSpecification(parameter)) + .ToCustomization(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromValuesAttribute.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromValuesAttribute.cs new file mode 100644 index 00000000..45fa21d4 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Attributes/PickFromValuesAttribute.cs @@ -0,0 +1,45 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Attributes +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using global::AutoFixture.Xunit2; + + using Objectivity.AutoFixture.XUnit2.Core.Common; + using Objectivity.AutoFixture.XUnit2.Core.Requests; + using Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders; + + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class PickFromValuesAttribute : CustomizeAttribute + { + private readonly HashSet inputValues; + private readonly Lazy> readonlyValues; + + public PickFromValuesAttribute(params object[] values) + { + this.inputValues = new HashSet(values.NotNull(nameof(values))); + if (this.inputValues.Count == 0) + { + throw new ArgumentException("At least one value is expected to be specified.", nameof(values)); + } + + this.readonlyValues = new Lazy>(() => Array.AsReadOnly(this.inputValues.ToArray())); + } + + public IReadOnlyCollection Values => this.readonlyValues.Value; + + public override ICustomization GetCustomization(ParameterInfo parameter) + { + return new CompositeSpecimenBuilder( + new FilteringSpecimenBuilder( + new RequestFactoryRelay((type) => new FixedValuesRequest(type, this.inputValues.ToArray())), + new EqualRequestSpecification(parameter)), + new RandomFixedValuesGenerator()) + .ToCustomization(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Common/EnumerableExtensions.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Common/EnumerableExtensions.cs new file mode 100644 index 00000000..0198dccd --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Common/EnumerableExtensions.cs @@ -0,0 +1,62 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Common +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + internal static class EnumerableExtensions + { + private static readonly MethodInfo BuildTypedArrayMethodInfo = + typeof(EnumerableExtensions).GetTypeInfo().GetMethod( + nameof(BuildTypedArray), + BindingFlags.Static | BindingFlags.NonPublic); + + public static bool TryGetEnumerableSingleTypeArgument(this Type type, out Type argument) + { + if (type.NotNull(nameof(type)).IsArray) + { + argument = type.GetElementType(); + return true; + } + + var interfaces = type.GetInterfaces(); + if (type.IsInterface) + { + interfaces = interfaces.Append(type).ToArray(); + } + + var genericInterface = Array.Find( + interfaces, + x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + if (genericInterface is not null) + { + var typeArguments = genericInterface.GenericTypeArguments; + if (typeArguments.Length == 1) + { + argument = typeArguments[0]; + return true; + } + } + + argument = null; + return false; + } + + public static object ToTypedArray(this IEnumerable items, Type itemType) + { + var method = BuildTypedArrayMethodInfo.MakeGenericMethod(itemType.NotNull(nameof(itemType))); + return method.Invoke(null, new object[] { items.NotNull(nameof(items)) }); + } + + private static IEnumerable BuildTypedArray(IEnumerable items) + { + var casted = items is IEnumerable castedItems + ? castedItems + : items.Cast(); + + return casted.ToArray(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Common/RoundRobinEnumerable[T].cs b/src/Objectivity.AutoFixture.XUnit2.Core/Common/RoundRobinEnumerable[T].cs new file mode 100644 index 00000000..d1fc8483 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Common/RoundRobinEnumerable[T].cs @@ -0,0 +1,38 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Common +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + internal sealed class RoundRobinEnumerable : IEnumerable + { + private readonly IEnumerable values; + + public RoundRobinEnumerable(params T[] values) + { + this.values = values.NotNull(nameof(values)); + if (values.Length == 0) + { + throw new ArgumentException("At least one value is expected to be specified.", nameof(values)); + } + } + + [SuppressMessage("Blocker Bug", "S2190:Loops and recursions should not be infinite", Justification = "This is a round robin implementation.")] + public IEnumerator GetEnumerator() + { + while (true) + { + foreach (var @value in this.values) + { + yield return @value; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Requests/ExceptValuesRequest.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Requests/ExceptValuesRequest.cs new file mode 100644 index 00000000..025d6b60 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Requests/ExceptValuesRequest.cs @@ -0,0 +1,12 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Requests +{ + using System; + + internal sealed class ExceptValuesRequest : ValuesRequest + { + public ExceptValuesRequest(Type operandType, params object[] values) + : base(operandType, values) + { + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Requests/FixedValuesRequest.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Requests/FixedValuesRequest.cs new file mode 100644 index 00000000..7234d774 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Requests/FixedValuesRequest.cs @@ -0,0 +1,12 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Requests +{ + using System; + + internal sealed class FixedValuesRequest : ValuesRequest + { + public FixedValuesRequest(Type operandType, params object[] values) + : base(operandType, values) + { + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/Requests/ValuesRequest.cs b/src/Objectivity.AutoFixture.XUnit2.Core/Requests/ValuesRequest.cs new file mode 100644 index 00000000..3a310e3c --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/Requests/ValuesRequest.cs @@ -0,0 +1,75 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.Requests +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + + using Objectivity.AutoFixture.XUnit2.Core.Common; + + internal abstract class ValuesRequest : IEquatable + where T : ValuesRequest + { + private readonly HashSet inputValues; + private readonly Lazy> readonlyValues; + + protected ValuesRequest(Type operandType, params object[] values) + { + this.OperandType = operandType.NotNull(nameof(operandType)); + this.inputValues = new HashSet(values.NotNull(nameof(values))); + if (this.inputValues.Count == 0) + { + throw new ArgumentException("At least one value is expected to be specified.", nameof(values)); + } + + this.readonlyValues = new Lazy>(() => Array.AsReadOnly(this.inputValues.ToArray())); + } + + public Type OperandType { get; } + + public IReadOnlyCollection Values => this.readonlyValues.Value; + + public override bool Equals(object obj) + { + if (obj is T other) + { + return this.Equals(other); + } + + return false; + } + + public bool Equals(T other) + { + if (other is null) + { + return false; + } + + return this.OperandType == other.OperandType + && this.inputValues.SetEquals(other.Values); + } + + public override int GetHashCode() + { + var hc = this.OperandType.GetHashCode() ^ typeof(T).GetHashCode(); + foreach (var inputValue in this.inputValues.Where(x => x is not null)) + { + hc ^= inputValue.GetHashCode(); + } + + return hc; + } + + public override string ToString() + { + var values = string.Join(", ", this.inputValues.Select(x => $"[{(x?.GetType() ?? typeof(object)).Name}] {x ?? "null"}")); + return string.Format( + CultureInfo.CurrentCulture, + "{0} (OperandType: {1}, Values: {2}", + this.GetType().Name, + this.OperandType.FullName, + values); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomExceptValuesGenerator.cs b/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomExceptValuesGenerator.cs new file mode 100644 index 00000000..9bf1b854 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomExceptValuesGenerator.cs @@ -0,0 +1,47 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + using Objectivity.AutoFixture.XUnit2.Core.Common; + using Objectivity.AutoFixture.XUnit2.Core.Requests; + + internal class RandomExceptValuesGenerator : ISpecimenBuilder + { + [SuppressMessage("Major Bug", "S2583:Conditionally executed code should be reachable", Justification = "Analyzer issue as the code is reachable")] + public object Create(object request, ISpecimenContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (request.NotNull(nameof(request)) is ExceptValuesRequest exceptValuesRequest) + { + var duplicateLimiter = new HashSet(); + object result; + + do + { + result = context.Resolve(exceptValuesRequest.OperandType); + var hasDuplicate = duplicateLimiter.Contains(result); + if (hasDuplicate) + { + throw new ObjectCreationException("The value could not be created. Probably all possible values were excluded."); + } + + duplicateLimiter.Add(result); + } + while (exceptValuesRequest.Values.Contains(result)); + + return result; + } + + return new NoSpecimen(); + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomFixedValuesGenerator.cs b/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomFixedValuesGenerator.cs new file mode 100644 index 00000000..a709766d --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RandomFixedValuesGenerator.cs @@ -0,0 +1,50 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders +{ + using System; + using System.Collections; + using System.Collections.Concurrent; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + + using global::AutoFixture.Kernel; + + using Objectivity.AutoFixture.XUnit2.Core.Common; + using Objectivity.AutoFixture.XUnit2.Core.Requests; + + internal sealed class RandomFixedValuesGenerator : ISpecimenBuilder + { + private readonly ConcurrentDictionary enumerators = new(); + + public object Create(object request, ISpecimenContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (request.NotNull(nameof(request)) is FixedValuesRequest fixedValuesRequest) + { + return this.CreateValue(fixedValuesRequest); + } + + return new NoSpecimen(); + } + + [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "It is good enough for collection randomization.")] + private static IEnumerator CreateEnumerable(FixedValuesRequest request) + { + var random = new Random(); + var values = request.Values.OrderBy((_) => random.Next()).ToArray(); + + return new RoundRobinEnumerable(values).GetEnumerator(); + } + + private object CreateValue(FixedValuesRequest request) + { + var generator = this.enumerators.GetOrAdd(request, CreateEnumerable); + generator.MoveNext(); + + return generator.Current; + } + } +} diff --git a/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RequestFactoryRelay.cs b/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RequestFactoryRelay.cs new file mode 100644 index 00000000..e04bdb04 --- /dev/null +++ b/src/Objectivity.AutoFixture.XUnit2.Core/SpecimenBuilders/RequestFactoryRelay.cs @@ -0,0 +1,65 @@ +namespace Objectivity.AutoFixture.XUnit2.Core.SpecimenBuilders +{ + using System; + using System.Collections; + using System.Reflection; + + using global::AutoFixture; + using global::AutoFixture.Kernel; + + using Objectivity.AutoFixture.XUnit2.Core.Common; + + internal sealed class RequestFactoryRelay : ISpecimenBuilder + { + public RequestFactoryRelay(Func requestFactory) + { + this.RequestFactory = requestFactory.NotNull(nameof(requestFactory)); + } + + public Func RequestFactory { get; } + + public object Create(object request, ISpecimenContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (request.NotNull(nameof(request)) is ParameterInfo parameterInfo) + { + var parameterType = Nullable.GetUnderlyingType(parameterInfo.ParameterType) + ?? parameterInfo.ParameterType; + + return parameterType != typeof(string) + && parameterType.TryGetEnumerableSingleTypeArgument(out var itemType) + ? this.CreateMultiple(parameterType, itemType, context) + : this.CreateSingle(parameterType, context); + } + + return new NoSpecimen(); + } + + private object CreateSingle(Type type, ISpecimenContext context) + { + var transformedRequest = this.RequestFactory(type); + return transformedRequest is not null + ? context.Resolve(transformedRequest) + : new NoSpecimen(); + } + + private object CreateMultiple(Type collectionType, Type itemType, ISpecimenContext context) + { + var transformedRequest = this.RequestFactory(itemType); + if (transformedRequest is not null + && context.Resolve(new MultipleRequest(transformedRequest)) is IEnumerable elements) + { + var items = elements.ToTypedArray(itemType); + return collectionType.IsArray || collectionType.IsAbstract + ? items + : Activator.CreateInstance(collectionType, items); + } + + return new NoSpecimen(); + } + } +}