Skip to content

Commit b5cb66d

Browse files
authored
Merge pull request #18 from tgharold/tests-tests-tests-1
Remove generics, add tests, address a Rider warning, refactoring
2 parents b15ed6b + 7dc63b9 commit b5cb66d

File tree

9 files changed

+152
-18
lines changed

9 files changed

+152
-18
lines changed

examples/OptionsPatternMvc.Example/Settings/ExampleAppSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System.ComponentModel.DataAnnotations;
2+
using OptionsPatternMvc.Example.Attributes;
23

34
namespace OptionsPatternMvc.Example.Settings
45
{
6+
[SettingsSectionName("Example")]
57
public class ExampleAppSettings
68
{
79
[Required]

examples/OptionsPatternMvc.Example/Settings/Validators/RecursiveDataAnnotationValidateOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class RecursiveDataAnnotationValidateOptions<TOptions>
1010
: IValidateOptions<TOptions>
1111
where TOptions : class
1212
{
13-
private static readonly RecursiveDataAnnotationValidator _recursiveDataAnnotationValidator = new RecursiveDataAnnotationValidator();
13+
private readonly RecursiveDataAnnotationValidator _recursiveDataAnnotationValidator = new RecursiveDataAnnotationValidator();
1414

1515
public RecursiveDataAnnotationValidateOptions(string optionsBuilderName)
1616
{

examples/OptionsPatternMvc.Example/appsettings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
"Microsoft.Hosting.Lifetime": "Information"
77
}
88
},
9-
"AllowedHosts": "*"
9+
"AllowedHosts": "*",
10+
"Example": {
11+
"Name": "OptionsPatternMvc.Example A"
12+
}
1013
}

src/RecursiveDataAnnotationsValidation/Attributes/SkipRecursiveValidation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace RecursiveDataAnnotationsValidation.Attributes
44
{
5-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Enum)]
5+
[AttributeUsage(AttributeTargets.Property)]
66
public class SkipRecursiveValidationAttribute : Attribute
77
{
88

src/RecursiveDataAnnotationsValidation/IRecursiveDataAnnotationValidator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ namespace RecursiveDataAnnotationsValidation
55
{
66
public interface IRecursiveDataAnnotationValidator
77
{
8-
bool TryValidateObjectRecursive<T>(
9-
T obj,
8+
bool TryValidateObjectRecursive(
9+
object obj,
1010
ValidationContext validationContext,
1111
List<ValidationResult> validationResults
12-
) where T : class;
12+
);
1313

14-
bool TryValidateObjectRecursive<T>(
15-
T obj,
14+
bool TryValidateObjectRecursive(
15+
object obj,
1616
List<ValidationResult> validationResults,
1717
IDictionary<object, object> validationContextItems = null
1818
);

src/RecursiveDataAnnotationsValidation/RecursiveDataAnnotationValidator.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@ namespace RecursiveDataAnnotationsValidation
99
{
1010
public class RecursiveDataAnnotationValidator : IRecursiveDataAnnotationValidator
1111
{
12-
public bool TryValidateObjectRecursive<T>(
13-
T obj,
12+
public bool TryValidateObjectRecursive(
13+
object obj, // see Note 1
1414
ValidationContext validationContext,
1515
List<ValidationResult> validationResults
16-
) where T : class
16+
)
1717
{
1818
return TryValidateObjectRecursive(
19-
obj,
19+
obj,
2020
validationResults,
2121
validationContext.Items
2222
);
2323
}
2424

25-
public bool TryValidateObjectRecursive<T>(
26-
T obj,
25+
public bool TryValidateObjectRecursive(
26+
object obj,
2727
List<ValidationResult> validationResults,
2828
IDictionary<object, object> validationContextItems = null
2929
)
@@ -40,7 +40,7 @@ private bool TryValidateObject(
4040
object obj,
4141
ICollection<ValidationResult> validationResults,
4242
IDictionary<object, object> validationContextItems = null
43-
)
43+
)
4444
{
4545
return Validator.TryValidateObject(
4646
obj,
@@ -54,8 +54,8 @@ private bool TryValidateObject(
5454
);
5555
}
5656

57-
private bool TryValidateObjectRecursive<T>(
58-
T obj,
57+
private bool TryValidateObjectRecursive(
58+
object obj,
5959
ICollection<ValidationResult> validationResults,
6060
ISet<object> validatedObjects,
6161
IDictionary<object, object> validationContextItems = null
@@ -141,5 +141,24 @@ private bool TryValidateObjectRecursive<T>(
141141

142142
return result;
143143
}
144+
145+
/* Note 1:
146+
*
147+
* Background information of why we don't use ValidationContext.ObjectInstance here, even though it is tempting.
148+
*
149+
* https://jeffhandley.com/2009-10-17/validator
150+
*
151+
* It’s important to note that for cross-field validation, relying on the ObjectInstance comes with a caveat.
152+
* It’s possible that the end user has entered a value for a property that could not be set—for instance
153+
* specifying “ABC” for a numeric field. In cases like that, asking the instance for that numeric property
154+
* will of course not give you the “ABC” value that the user has entered, thus the object’s other properties
155+
* are in an indeterminate state. But even so, we’ve found that it’s extremely valuable to provide this object
156+
* instance to the validation attributes.
157+
*
158+
* See also:
159+
*
160+
* https://github.com/dotnet/corefx/blob/8b04d0a18a49448ff7c8ee63239cd6d2a2be7e14/src/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/ValidationContext.cs
161+
*
162+
*/
144163
}
145164
}

src/RecursiveDataAnnotationsValidation/RecursiveDataAnnotationsValidation.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Authors>Thomas Harold</Authors>
77
<PackageLicenseExpression>MIT</PackageLicenseExpression>
88
<RepositoryUrl>https://github.com/tgharold/RecursiveDataAnnotationsValidation</RepositoryUrl>
9-
<PackageTags>dataannotation validator</PackageTags>
9+
<PackageTags>dataannotation validation validator</PackageTags>
1010
</PropertyGroup>
1111

1212
<ItemGroup>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Collections.Generic;
2+
using System.ComponentModel.DataAnnotations;
3+
using System.Linq;
4+
using RecursiveDataAnnotationsValidation.Tests.TestModels;
5+
using Xunit;
6+
7+
namespace RecursiveDataAnnotationsValidation.Tests
8+
{
9+
public class RecursiveDataAnnotationValidatorTests
10+
{
11+
/// <summary>Tests that use the method which takes a ValidationContext.</summary>
12+
public class ValidationContextTests
13+
{
14+
private readonly IRecursiveDataAnnotationValidator _validator = new RecursiveDataAnnotationValidator();
15+
16+
[Fact]
17+
public void Pass_all_validation()
18+
{
19+
var sut = new SimpleExample
20+
{
21+
IntegerA = 100,
22+
StringB = "test-100",
23+
BoolC = true,
24+
ExampleEnumD = ExampleEnum.ValueB
25+
};
26+
27+
var validationContext = new ValidationContext(sut);
28+
var validationResults = new List<ValidationResult>();
29+
var result = _validator.TryValidateObjectRecursive(sut, validationContext, validationResults);
30+
31+
Assert.True(result);
32+
Assert.Empty(validationResults);
33+
}
34+
35+
[Fact]
36+
public void Indicate_that_IntegerA_is_missing()
37+
{
38+
var sut = new SimpleExample
39+
{
40+
IntegerA = null,
41+
StringB = "test-101",
42+
BoolC = false,
43+
ExampleEnumD = ExampleEnum.ValueC
44+
};
45+
46+
const string fieldName = nameof(SimpleExample.IntegerA);
47+
var validationContext = new ValidationContext(sut);
48+
var validationResults = new List<ValidationResult>();
49+
var result = _validator.TryValidateObjectRecursive(sut, validationContext, validationResults);
50+
51+
Assert.False(result);
52+
Assert.NotEmpty(validationResults);
53+
Assert.NotNull(validationResults
54+
.FirstOrDefault(x => x.MemberNames.Contains(fieldName)));
55+
}
56+
}
57+
}
58+
}

test/RecursiveDataAnnotationsValidation.Tests/SkippedChildrenExampleTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.ComponentModel.DataAnnotations;
3+
using System.Linq;
34
using RecursiveDataAnnotationsValidation.Tests.TestModels;
45
using Xunit;
56

@@ -33,5 +34,56 @@ public void Passes_all_validation()
3334
Assert.True(result);
3435
Assert.Empty(validationResults);
3536
}
37+
38+
[Fact]
39+
public void Fails_for_SimpleA_BoolC()
40+
{
41+
var sut = new SkippedChildrenExample
42+
{
43+
Name = "Skipped-Children-2",
44+
SimpleA = new SimpleExample
45+
{
46+
IntegerA = 75124,
47+
BoolC = null, // set one of the props to null
48+
StringB = "simple-a-child-2",
49+
ExampleEnumD = ExampleEnum.ValueC
50+
},
51+
SimpleB = new SimpleExample
52+
{
53+
BoolC = true
54+
}
55+
};
56+
var validationResults = new List<ValidationResult>();
57+
var result = _validator.TryValidateObjectRecursive(sut, validationResults);
58+
59+
Assert.False(result);
60+
Assert.NotEmpty(validationResults);
61+
Assert.NotNull(validationResults
62+
.FirstOrDefault(x => x.MemberNames.Contains("SimpleA.BoolC")));
63+
}
64+
65+
[Fact]
66+
public void Fails_for_SimpleB_missing()
67+
{
68+
var sut = new SkippedChildrenExample
69+
{
70+
Name = "Skipped-Children-2",
71+
SimpleA = new SimpleExample
72+
{
73+
IntegerA = 75124,
74+
BoolC = null,
75+
StringB = "simple-a-child-2",
76+
ExampleEnumD = ExampleEnum.ValueC
77+
},
78+
SimpleB = null // the object is missing entirely
79+
};
80+
var validationResults = new List<ValidationResult>();
81+
var result = _validator.TryValidateObjectRecursive(sut, validationResults);
82+
83+
Assert.False(result);
84+
Assert.NotEmpty(validationResults);
85+
Assert.NotNull(validationResults
86+
.FirstOrDefault(x => x.MemberNames.Contains("SimpleB")));
87+
}
3688
}
3789
}

0 commit comments

Comments
 (0)