Skip to content

Commit 077918a

Browse files
authored
Merge branch 'development' into development
2 parents ca7956d + f870a71 commit 077918a

File tree

11 files changed

+206
-43
lines changed

11 files changed

+206
-43
lines changed

src/MyTested.AspNetCore.Mvc.Abstractions/Builders/Contracts/Attributes/IBaseAttributesTestBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,12 @@ TAttributesTestBuilder PassingFor<TAttribute>(Action<TAttribute> assertions)
4141
/// <returns>The same attributes test builder.</returns>
4242
TAttributesTestBuilder PassingFor<TAttribute>(Func<TAttribute, bool> predicate)
4343
where TAttribute : Attribute;
44+
45+
46+
/// <summary>
47+
/// Adds inherited attributes.
48+
/// </summary>
49+
/// <returns>The same attributes test builder.</returns>
50+
TAttributesTestBuilder IncludingInherited();
4451
}
4552
}

src/MyTested.AspNetCore.Mvc.Abstractions/Internal/TestContexts/ComponentTestContext.cs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ public abstract class ComponentTestContext : HttpTestContext
2222

2323
public object Component => this.component ??= this.ComponentConstructionDelegate();
2424

25-
public IEnumerable<object> ComponentAttributes
26-
=> this.componentAttributes ??= Reflection.GetCustomAttributes(this.Component);
25+
public IEnumerable<object> ComponentAttributes
26+
{
27+
get => this.componentAttributes ??= Reflection.GetCustomAttributes(this.Component);
28+
private set => this.componentAttributes = value;
29+
}
2730

2831
public IDictionary<Type, object> AggregatedDependencies
2932
=> this.aggregatedDependencies ??= new Dictionary<Type, object>();
@@ -66,27 +69,20 @@ public LambdaExpression MethodCall
6669
public object MethodResult
6770
{
6871
get => this.methodResult;
69-
7072
set => this.methodResult = this.ConvertMethodResult(value);
7173
}
7274

73-
public IEnumerable<object> MethodAttributes
74-
=> this.methodAttributes ??= Reflection.GetCustomAttributes(this.Method);
75+
public IEnumerable<object> MethodAttributes
76+
{
77+
get => this.methodAttributes ??= Reflection.GetCustomAttributes(this.Method);
78+
private set => this.methodAttributes = value;
79+
}
7580

7681
public Exception CaughtException { get; set; }
7782

7883
public object Model
7984
{
80-
get
81-
{
82-
if (this.model == null)
83-
{
84-
return this.MethodResult;
85-
}
86-
87-
return this.model;
88-
}
89-
85+
get => this.model ?? this.MethodResult;
9086
set => this.model = value;
9187
}
9288

@@ -116,6 +112,12 @@ public void Apply<TMethodResult>(InvocationTestContext<TMethodResult> invocation
116112
this.CaughtException = invocationTestContext.CaughtException;
117113
}
118114

115+
public void IncludeInheritedComponentAttributes()
116+
=> this.ComponentAttributes = Reflection.GetCustomAttributes(this.Component, true);
117+
118+
public void IncludeInheritedMethodAttributes()
119+
=> this.MethodAttributes = Reflection.GetCustomAttributes(this.Method, true);
120+
119121
protected virtual object ConvertMethodResult(object convertibleMethodResult) => convertibleMethodResult;
120122
}
121123
}

src/MyTested.AspNetCore.Mvc.Abstractions/Utilities/Reflection.cs

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
/// </summary>
1818
public static class Reflection
1919
{
20+
private const bool ShouldInheritAttributes = false;
21+
2022
private static readonly ConcurrentDictionary<Type, ConstructorInfo> TypesWithOneConstructorCache = new ConcurrentDictionary<Type, ConstructorInfo>();
2123
private static readonly ConcurrentDictionary<Type, IEnumerable<object>> TypeAttributesCache = new ConcurrentDictionary<Type, IEnumerable<object>>();
2224
private static readonly ConcurrentDictionary<MethodInfo, IEnumerable<object>> MethodAttributesCache = new ConcurrentDictionary<MethodInfo, IEnumerable<object>>();
@@ -118,8 +120,8 @@ public static bool HasGenericTypeDefinition(Type type, Type genericTypeDefinitio
118120
/// <param name="inheritedType">Inherited type to be checked.</param>
119121
/// <returns>True or false.</returns>
120122
public static bool AreAssignableByGeneric(Type baseType, Type inheritedType)
121-
=> IsGeneric(inheritedType)
122-
&& IsGeneric(baseType)
123+
=> IsGeneric(inheritedType)
124+
&& IsGeneric(baseType)
123125
&& baseType.IsAssignableFrom(inheritedType.GetGenericTypeDefinition());
124126

125127
/// <summary>
@@ -283,17 +285,32 @@ public static T TryCreateInstance<T>(IDictionary<Type, object> constructorParame
283285
/// Gets custom attributes on the provided object.
284286
/// </summary>
285287
/// <param name="obj">Object decorated with custom attribute.</param>
288+
/// <param name="shouldInherit">Indicates whether it should include inherited component attributes</param>
286289
/// <returns>IEnumerable of objects representing the custom attributes.</returns>
287-
public static IEnumerable<object> GetCustomAttributes(object obj)
290+
public static IEnumerable<object> GetCustomAttributes(object obj, bool shouldInherit = ShouldInheritAttributes)
288291
{
289292
var type = obj.GetType();
293+
var attributes = type.GetTypeInfo().GetCustomAttributes(shouldInherit);
294+
UpdateAttributesIfNeeded(type, attributes, TypeAttributesCache);
295+
290296
return TypeAttributesCache
291-
.GetOrAdd(type, _ => type.GetTypeInfo().GetCustomAttributes(false));
297+
.GetOrAdd(type, _ => attributes);
292298
}
293299

294-
public static IEnumerable<object> GetCustomAttributes(MethodInfo method)
295-
=> MethodAttributesCache
296-
.GetOrAdd(method, _ => method.GetCustomAttributes(false));
300+
/// <summary>
301+
/// Gets custom attributes on the provided method.
302+
/// </summary>
303+
/// <param name="method">Method decorated with custom attribute.</param>
304+
/// <param name="shouldInherit">Indicates whether it should include inherited method attributes</param>
305+
/// <returns>IEnumerable of objects representing the custom attributes.</returns>
306+
public static IEnumerable<object> GetCustomAttributes(MethodInfo method, bool shouldInherit = ShouldInheritAttributes)
307+
{
308+
var attributes = method.GetCustomAttributes(shouldInherit);
309+
UpdateAttributesIfNeeded(method, attributes, MethodAttributesCache);
310+
311+
return MethodAttributesCache
312+
.GetOrAdd(method, _ => attributes);
313+
}
297314

298315
/// <summary>
299316
/// Checks whether two objects are deeply equal by reflecting all their public properties recursively. Resolves successfully value and reference types, overridden Equals method, custom == operator, IComparable, nested objects and collection properties.
@@ -388,7 +405,7 @@ public static TDelegate CreateDelegateFromMethod<TDelegate>(object instance, Fun
388405
.FirstOrDefault(methodFilter)
389406
?.CreateDelegate(typeof(TDelegate), instance) as TDelegate;
390407
}
391-
408+
392409
/// <summary>
393410
/// Checks whether property with the provided name exists in a dynamic object.
394411
/// </summary>
@@ -462,8 +479,8 @@ private static ConstructorInfo GetConstructorByUnorderedParameters(this Type typ
462479
}
463480

464481
private static bool AreDeeplyEqual(
465-
object expected,
466-
object actual,
482+
object expected,
483+
object actual,
467484
ConditionalWeakTable<object, object> processedElements,
468485
DeepEqualityResult result)
469486
{
@@ -531,7 +548,7 @@ private static bool AreDeeplyEqual(
531548
? result.Success
532549
: result.Failure;
533550
}
534-
551+
535552
var equalsOperator = expectedType.GetMethods().FirstOrDefault(m => m.Name == "op_Equality");
536553
if (equalsOperator != null)
537554
{
@@ -583,15 +600,15 @@ private static bool AreDeeplyEqual(
583600
}
584601

585602
private static bool AreNotDeeplyEqual(
586-
object expected,
587-
object actual,
603+
object expected,
604+
object actual,
588605
ConditionalWeakTable<object, object> processedElements,
589606
DeepEqualityResult result)
590607
=> !AreDeeplyEqual(expected, actual, processedElements, result);
591608

592609
private static bool CollectionsAreDeeplyEqual(
593-
object expected,
594-
object actual,
610+
object expected,
611+
object actual,
595612
ConditionalWeakTable<object, object> processedElements,
596613
DeepEqualityResult result)
597614
{
@@ -675,8 +692,8 @@ private static bool ObjectImplementsIComparable(object obj)
675692
.FirstOrDefault(i => i.Name.StartsWith("IComparable")) != null;
676693

677694
private static bool ObjectPropertiesAreDeeplyEqual(
678-
object expected,
679-
object actual,
695+
object expected,
696+
object actual,
680697
ConditionalWeakTable<object, object> processedElements,
681698
DeepEqualityResult result)
682699
{
@@ -696,8 +713,8 @@ private static bool ObjectPropertiesAreDeeplyEqual(
696713
if (expectedPropertyValue is IEnumerable && expectedPropertyValue.GetType() != typeof(string))
697714
{
698715
if (!CollectionsAreDeeplyEqual(
699-
expectedPropertyValue,
700-
actualPropertyValue,
716+
expectedPropertyValue,
717+
actualPropertyValue,
701718
processedElements,
702719
result))
703720
{
@@ -706,8 +723,8 @@ private static bool ObjectPropertiesAreDeeplyEqual(
706723
}
707724

708725
var propertiesAreDifferent = AreNotDeeplyEqual(
709-
expectedPropertyValue,
710-
actualPropertyValue,
726+
expectedPropertyValue,
727+
actualPropertyValue,
711728
processedElements,
712729
result);
713730

@@ -722,11 +739,22 @@ private static bool ObjectPropertiesAreDeeplyEqual(
722739
return result.Success;
723740
}
724741

742+
private static void UpdateAttributesIfNeeded<T>(
743+
T key,
744+
object[] attributes,
745+
ConcurrentDictionary<T, IEnumerable<object>> result)
746+
{
747+
if (result.ContainsKey(key))
748+
{
749+
result.AddOrUpdate(key, attributes, (dictKey, oldValue) => attributes);
750+
}
751+
}
752+
725753
private static string GetFriendlyTypeName(Type type, bool useFullName)
726754
{
727755
const string anonymousTypePrefix = "<>f__";
728756

729-
var typeName = useFullName
757+
var typeName = useFullName
730758
? type?.FullName ?? type?.Name
731759
: type?.Name;
732760

src/MyTested.AspNetCore.Mvc.Controllers/Builders/Attributes/ActionAttributesTestBuilder.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ public ActionAttributesTestBuilder(ControllerTestContext testContext)
3131
public IActionAttributesTestBuilder AndAlso() => this;
3232

3333
public override void ThrowNewAttributeAssertionException(string expectedValue, string actualValue)
34-
=> throw new AttributeAssertionException(string.Format(
35-
"{0} action to have {1}, but {2}.",
36-
this.TestContext.ExceptionMessagePrefix,
37-
expectedValue,
38-
actualValue));
34+
=> throw new AttributeAssertionException($"{this.TestContext.ExceptionMessagePrefix} action to have {expectedValue}, but {actualValue}.");
35+
36+
/// <inheritdoc />
37+
public IAndActionAttributesTestBuilder IncludingInherited()
38+
{
39+
this.TestContext.IncludeInheritedMethodAttributes();
40+
return this;
41+
}
3942
}
4043
}

src/MyTested.AspNetCore.Mvc.Controllers/Builders/Attributes/ControllerAttributesTestBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,12 @@ public ControllerAttributesTestBuilder(ControllerTestContext testContext)
2626

2727
/// <inheritdoc />
2828
public IControllerAttributesTestBuilder AndAlso() => this;
29+
30+
/// <inheritdoc />
31+
public IAndControllerAttributesTestBuilder IncludingInherited()
32+
{
33+
this.TestContext.IncludeInheritedComponentAttributes();
34+
return this;
35+
}
2936
}
3037
}

src/MyTested.AspNetCore.Mvc.ViewComponents/Builders/Attributes/ViewComponentAttributesTestBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,12 @@ public ViewComponentAttributesTestBuilder(ComponentTestContext testContext)
2424

2525
/// <inheritdoc />
2626
public IViewComponentAttributesTestBuilder AndAlso() => this;
27+
28+
/// <inheritdoc />
29+
public IAndViewComponentAttributesTestBuilder IncludingInherited()
30+
{
31+
this.TestContext.IncludeInheritedComponentAttributes();
32+
return this;
33+
}
2734
}
2835
}

test/MyTested.AspNetCore.Mvc.Controllers.Test/BuildersTests/AttributesTests/ActionAttributesTestBuilderTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Setups.Controllers;
77
using Xunit;
88
using System;
9+
using Microsoft.AspNetCore.Authorization;
910

1011
public class ActionAttributesTestBuilderTests
1112
{
@@ -118,5 +119,17 @@ public void PassingForShouldThrowExceptionWithIncorrectAttributeWithPredicate()
118119
},
119120
"When calling OtherAttributes action in MvcController expected action to have ActionNameAttribute, but in fact such was not found.");
120121
}
122+
123+
[Fact]
124+
public void IncludingInheritedShouldIncludeAllCustomInheritedAttributesFromBaseMethodsAndNotThrowException()
125+
{
126+
MyController<InheritControllerAttributes>
127+
.Instance()
128+
.Calling(c=> c.MethodA())
129+
.ShouldHave()
130+
.ActionAttributes(attributes => attributes.IncludingInherited()
131+
.ContainingAttributeOfType<HttpPostAttribute>()
132+
.ContainingAttributeOfType<AuthorizeAttribute>());
133+
}
121134
}
122135
}

test/MyTested.AspNetCore.Mvc.Controllers.Test/BuildersTests/AttributesTests/ControllerAttributesTestBuilderTests.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void PassingForShouldThrowExceptionWithIncorrectAttribute()
6666
},
6767
"When testing MvcController was expected to have ActionNameAttribute, but in fact such was not found.");
6868
}
69-
69+
7070
[Fact]
7171
public void PassingForShouldNotThrowExceptionWithCorrectPredicate()
7272
{
@@ -105,5 +105,33 @@ public void PassingForShouldThrowExceptionWithIncorrectAttributeWithPredicate()
105105
},
106106
"When testing MvcController was expected to have ActionNameAttribute, but in fact such was not found.");
107107
}
108+
109+
[Fact]
110+
public void IncludingInheritedShouldIncludeAllCustomInheritedAttributesFromBaseClassesAndNotThrowException()
111+
{
112+
MyController<InheritControllerAttributes>
113+
.Instance()
114+
.ShouldHave()
115+
.Attributes(attributes => attributes.IncludingInherited()
116+
.ContainingAttributeOfType<ValidateAntiForgeryTokenAttribute>()
117+
.ContainingAttributeOfType<AllowAnonymousAttribute>()
118+
.ContainingAttributeOfType<ResponseCacheAttribute>());
119+
}
120+
121+
[Fact]
122+
public void TryingToAssertInheritedAttributesWithoutIncludingInheritedShouldThrowException()
123+
{
124+
Test.AssertException<AttributeAssertionException>(
125+
() =>
126+
{
127+
MyController<InheritControllerAttributes>
128+
.Instance()
129+
.ShouldHave()
130+
.Attributes(attributes => attributes//.IncludingInherited()
131+
.ContainingAttributeOfType<ValidateAntiForgeryTokenAttribute>()
132+
.ContainingAttributeOfType<AllowAnonymousAttribute>()
133+
.ContainingAttributeOfType<ResponseCacheAttribute>());
134+
}, $"When testing {nameof(InheritControllerAttributes)} was expected to have ValidateAntiForgeryTokenAttribute, but in fact such was not found.");
135+
}
108136
}
109137
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace MyTested.AspNetCore.Mvc.Test.Setups.Controllers
2+
{
3+
using System;
4+
using Microsoft.AspNetCore.Authorization;
5+
using Microsoft.AspNetCore.Mvc;
6+
7+
[ResponseCache]
8+
public class InheritControllerAttributes : InheritAttributesBaseController
9+
{
10+
public override uint MethodA()
11+
=> 10;
12+
}
13+
14+
[AllowAnonymous]
15+
[ValidateAntiForgeryToken]
16+
public abstract class InheritAttributesBaseController : ControllerBase
17+
{
18+
[Authorize]
19+
[HttpPost]
20+
public abstract uint MethodA();
21+
}
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace MyTested.AspNetCore.Mvc.Test.Setups.ViewComponents
2+
{
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.AspNetCore.Mvc;
5+
6+
[ResponseCache]
7+
public class InheritViewComponent : BaseInheritViewComponent
8+
{
9+
}
10+
11+
[AllowAnonymous]
12+
[ValidateAntiForgeryToken]
13+
public abstract class BaseInheritViewComponent : ViewComponent
14+
{
15+
public virtual IViewComponentResult Invoke() => this.View();
16+
}
17+
}

0 commit comments

Comments
 (0)