Skip to content

Commit 6136e10

Browse files
authored
Skip data generation when already specified by inline or member attribute (#33)
Skip data generation when data already specified through inline or member attribute
1 parent 55a3db2 commit 6136e10

File tree

6 files changed

+203
-14
lines changed

6 files changed

+203
-14
lines changed
Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,106 @@
11
namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Attributes
22
{
33
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Linq;
6+
using System.Reflection;
47
using FluentAssertions;
58
using global::AutoFixture;
9+
using global::AutoFixture.Xunit2;
610
using Objectivity.AutoFixture.XUnit2.Core.Attributes;
711
using Xunit;
812

913
[Collection("AutoDataAdapterAttribute")]
1014
[Trait("Category", "Attributes")]
15+
[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Test objects")]
1116
public class AutoDataAdapterAttributeTests
1217
{
13-
[Fact(DisplayName = "GIVEN fixture WHEN constructor is invoked THEN passed fixture is being adapted")]
14-
public void GivenFixture_WhenConstructorIsInvoked_ThenPassedFixtureIsBeingAdapted()
18+
[Theory(DisplayName = "GIVEN fixture WHEN constructor is invoked THEN passed fixture is being adapted and inline values collection is empty")]
19+
[AutoData]
20+
public void GivenFixture_WhenConstructorIsInvoked_ThenPassedFixtureIsBeingAdaptedAndInlineValuesCollectionIsEmpty(Fixture fixture)
1521
{
1622
// Arrange
17-
IFixture fixture = new Fixture();
18-
1923
// Act
20-
var attribute = new AutoDataAdapterAttribute(fixture);
24+
var attribute = new AutoDataAdapterAttribute(fixture, null);
2125

2226
// Assert
2327
attribute.AdaptedFixture.Should().Be(fixture);
28+
attribute.InlineValues.Should().BeEmpty();
2429
}
2530

2631
[Fact(DisplayName = "GIVEN uninitialized fixture WHEN constructor is invoked THEN exception is thrown")]
2732
public void GivenUninitializedFixture_WhenConstructorIsInvoked_ThenExceptionIsThrown()
2833
{
2934
// Arrange
30-
const IFixture fixture = null;
35+
// Act
36+
// Assert
37+
Assert.Throws<ArgumentNullException>(() => new AutoDataAdapterAttribute(null));
38+
}
39+
40+
[Theory(DisplayName = "GIVEN uninitialized method info WHEN GetData is invoked THEN exception is thrown")]
41+
[AutoData]
42+
public void GivenUninitializedMethodInfo_WhenConstructorIsInvoked_ThenExceptionIsThrown(Fixture fixture)
43+
{
44+
// Arrange
45+
var attribute = new AutoDataAdapterAttribute(fixture);
46+
47+
// Act
48+
// Assert
49+
Assert.Throws<ArgumentNullException>(() => attribute.GetData(null));
50+
}
51+
52+
[Fact(DisplayName = "GIVEN test data with instance WHEN GetData called THEN auto data generation skipped")]
53+
public void GivenTestDataWithInstance_WhenGetDataCalled_ThenAutoDataGenerationSkipped()
54+
{
55+
// Arrange
56+
IFixture fixture = new Fixture();
57+
var attribute = new AutoDataAdapterAttribute(fixture, SpecificTestClass.Create());
58+
var methodInfo = typeof(AutoDataAdapterAttributeTests).GetMethod(nameof(this.TestMethodWithAbstractTestClass), BindingFlags.Instance | BindingFlags.NonPublic);
3159

3260
// Act
61+
var data = attribute.GetData(methodInfo).ToArray();
62+
3363
// Assert
34-
Assert.Throws<ArgumentNullException>(() => new AutoDataAdapterAttribute(fixture));
64+
data.Should().HaveCount(1)
65+
.And.Subject.First().Should().HaveCount(methodInfo.GetParameters().Length)
66+
.And.NotContainNulls()
67+
.And.Subject.Skip(1).Should().AllBeEquivalentTo(data.First().Last());
68+
}
69+
70+
[Fact(DisplayName = "GIVEN empty test data WHEN GetData called THEN auto data generation throws exception")]
71+
public void GivenEmptyTestData_WhenGetDataCalled_ThenAutoDataGenerationSkipped()
72+
{
73+
// Arrange
74+
IFixture fixture = new Fixture();
75+
var attribute = new AutoDataAdapterAttribute(fixture);
76+
var methodInfo = typeof(AutoDataAdapterAttributeTests).GetMethod(nameof(this.TestMethodWithAbstractTestClass), BindingFlags.Instance | BindingFlags.NonPublic);
77+
78+
// Act
79+
Action data = () => attribute.GetData(methodInfo);
80+
81+
// Assert
82+
data.Should().Throw<Exception>();
83+
}
84+
85+
protected string TestMethodWithAbstractTestClass(SpecificTestClass instance, [Frozen]string text, string message)
86+
{
87+
return $"{instance}: {text}, {message}";
88+
}
89+
90+
public abstract class AbstractTestClass
91+
{
92+
}
93+
94+
public class SpecificTestClass : AbstractTestClass
95+
{
96+
private SpecificTestClass()
97+
{
98+
}
99+
100+
public static AbstractTestClass Create()
101+
{
102+
return new SpecificTestClass();
103+
}
35104
}
36105
}
37106
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Comparers
2+
{
3+
using System.Collections.Generic;
4+
using FluentAssertions;
5+
using global::AutoFixture;
6+
using global::AutoFixture.Xunit2;
7+
using Objectivity.AutoFixture.XUnit2.Core.Attributes;
8+
using Objectivity.AutoFixture.XUnit2.Core.Comparers;
9+
using Objectivity.AutoFixture.XUnit2.Core.Customizations;
10+
using Xunit;
11+
12+
public class CustomizeAttributeComparerTests
13+
{
14+
private static readonly CustomizeAttributeComparer Comparer = new CustomizeAttributeComparer();
15+
private static readonly CustomizeWithAttribute CustomizeAttribute = new CustomizeWithAttribute(typeof(DoNotThrowOnRecursionCustomization));
16+
private static readonly FrozenAttribute FrozenAttribute = new FrozenAttribute();
17+
18+
public static IEnumerable<object[]> TestData { get; } = new[]
19+
{
20+
new object[] { CustomizeAttribute, CustomizeAttribute, 0 },
21+
new object[] { FrozenAttribute, FrozenAttribute, 0 },
22+
new object[] { FrozenAttribute, CustomizeAttribute, 1 },
23+
new object[] { CustomizeAttribute, FrozenAttribute, -1 },
24+
};
25+
26+
[Theory(DisplayName = "GIVEN both attributes WHEN Compare is invoked THEN expected result returned")]
27+
[MemberData(nameof(TestData))]
28+
public void GivenBothNonFrozenAttributes_WhenCompareIsInvoked_ThenBothEquals(
29+
IParameterCustomizationSource x,
30+
IParameterCustomizationSource y,
31+
int expectedResult)
32+
{
33+
// Arrange
34+
// Act
35+
var result = Comparer.Compare(x, y);
36+
37+
// Assert
38+
result.Should().Be(expectedResult);
39+
}
40+
}
41+
}

src/Objectivity.AutoFixture.XUnit2.Core.Tests/Providers/InlineAutoDataAttributeProviderTests.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace Objectivity.AutoFixture.XUnit2.Core.Tests.Providers
22
{
33
using System.Diagnostics.CodeAnalysis;
4-
using System.Linq;
54
using FluentAssertions;
65
using global::AutoFixture;
76
using global::AutoFixture.Xunit2;
@@ -16,17 +15,20 @@ public class InlineAutoDataAttributeProviderTests
1615
[Theory(DisplayName = "GIVEN initialized fixture WHEN GetAttribute is invoked THEN attribute with specified fixture is returned")]
1716
[AutoData]
1817
[SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Assertion checks it earlier and throws exception.")]
19-
public void GivenInitializedFixture_WhenGetAttributeIsInvoked_ThenAttributeWithSpecifiedFixtureIsReturned(Fixture fixture)
18+
public void GivenInitializedFixture_WhenGetAttributeIsInvoked_ThenAttributeWithSpecifiedFixtureIsReturned(
19+
Fixture fixture,
20+
object[] inlineValues)
2021
{
2122
// Arrange
2223
var provider = new InlineAutoDataAttributeProvider();
2324

2425
// Act
25-
var dataAttribute = provider.GetAttribute(fixture) as CompositeDataAttribute;
26+
var attribute = provider.GetAttribute(fixture, inlineValues);
2627

2728
// Assert
28-
dataAttribute.Should().NotBeNull();
29-
dataAttribute.Attributes.FirstOrDefault(a => a is AutoDataAdapterAttribute).As<AutoDataAdapterAttribute>().AdaptedFixture.Should().Be(fixture);
29+
var autoDataAdapterAttribute = attribute.Should().BeOfType<AutoDataAdapterAttribute>().Which;
30+
autoDataAdapterAttribute.AdaptedFixture.Should().Be(fixture);
31+
autoDataAdapterAttribute.InlineValues.Should().BeEquivalentTo(inlineValues);
3032
}
3133
}
3234
}
Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,68 @@
11
namespace Objectivity.AutoFixture.XUnit2.Core.Attributes
22
{
33
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
47
using global::AutoFixture;
8+
using global::AutoFixture.Kernel;
59
using global::AutoFixture.Xunit2;
610
using Objectivity.AutoFixture.XUnit2.Core.Common;
11+
using Objectivity.AutoFixture.XUnit2.Core.Comparers;
712

813
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
914
internal sealed class AutoDataAdapterAttribute : AutoDataAttribute
1015
{
11-
public AutoDataAdapterAttribute(IFixture fixture)
16+
public AutoDataAdapterAttribute(IFixture fixture, params object[] inlineValues)
1217
: base(() => fixture)
1318
{
1419
this.AdaptedFixture = fixture.NotNull(nameof(fixture));
20+
this.InlineValues = inlineValues ?? Array.Empty<object>();
1521
}
1622

1723
public IFixture AdaptedFixture { get; }
24+
25+
public IReadOnlyCollection<object> InlineValues { get; }
26+
27+
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
28+
{
29+
if (testMethod == null)
30+
{
31+
throw new ArgumentNullException(nameof(testMethod));
32+
}
33+
34+
// This is an extension of AutoDataAttribute.GetData method
35+
// with the ability to skip already provided inline values
36+
var specimens = new List<object>(this.InlineValues);
37+
var parameters = testMethod.GetParameters();
38+
foreach (var p in parameters.Skip(this.InlineValues.Count))
39+
{
40+
this.CustomizeFixture(p);
41+
42+
var specimen = this.Resolve(p);
43+
specimens.Add(specimen);
44+
}
45+
46+
return new[] { specimens.ToArray() };
47+
}
48+
49+
private void CustomizeFixture(ParameterInfo p)
50+
{
51+
var customizeAttributes = p.GetCustomAttributes()
52+
.OfType<IParameterCustomizationSource>()
53+
.OrderBy(x => x, new CustomizeAttributeComparer());
54+
55+
foreach (var ca in customizeAttributes)
56+
{
57+
var c = ca.GetCustomization(p);
58+
this.AdaptedFixture.Customize(c);
59+
}
60+
}
61+
62+
private object Resolve(ParameterInfo p)
63+
{
64+
var context = new SpecimenContext(this.AdaptedFixture);
65+
return context.Resolve(p);
66+
}
1867
}
1968
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace Objectivity.AutoFixture.XUnit2.Core.Comparers
2+
{
3+
using System.Collections.Generic;
4+
using global::AutoFixture;
5+
using global::AutoFixture.Xunit2;
6+
7+
// Direct copy from the AutoFixture source code as the original class is internal.
8+
internal class CustomizeAttributeComparer : Comparer<IParameterCustomizationSource>
9+
{
10+
public override int Compare(IParameterCustomizationSource x, IParameterCustomizationSource y)
11+
{
12+
var xFrozen = x is FrozenAttribute;
13+
var yFrozen = y is FrozenAttribute;
14+
15+
if (xFrozen && !yFrozen)
16+
{
17+
return 1;
18+
}
19+
20+
if (yFrozen && !xFrozen)
21+
{
22+
return -1;
23+
}
24+
25+
return 0;
26+
}
27+
}
28+
}

src/Objectivity.AutoFixture.XUnit2.Core/Providers/InlineAutoDataAttributeProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public sealed class InlineAutoDataAttributeProvider : IAutoFixtureInlineAttribut
1010
{
1111
public DataAttribute GetAttribute(IFixture fixture, params object[] values)
1212
{
13-
return new CompositeDataAttribute(new InlineDataAttribute(values), new AutoDataAdapterAttribute(fixture));
13+
return new AutoDataAdapterAttribute(fixture, values);
1414
}
1515
}
1616
}

0 commit comments

Comments
 (0)