Skip to content

Commit cb0f7cd

Browse files
Implement IncludingInherited for action attributes
1 parent 5e72751 commit cb0f7cd

File tree

3 files changed

+59
-39
lines changed

3 files changed

+59
-39
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ public LambdaExpression MethodCall
6969
public object MethodResult
7070
{
7171
get => this.methodResult;
72-
7372
set => this.methodResult = this.ConvertMethodResult(value);
7473
}
7574

76-
public IEnumerable<object> MethodAttributes
77-
=> 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+
}
7880

7981
public Exception CaughtException { get; set; }
8082

@@ -111,7 +113,10 @@ public void Apply<TMethodResult>(InvocationTestContext<TMethodResult> invocation
111113
}
112114

113115
public void IncludeInheritedComponentAttributes()
114-
=> this.ComponentAttributes = Reflection.GetCustomAttributesIncludingInherited(this.Component);
116+
=> this.ComponentAttributes = Reflection.GetCustomAttributes(this.Component, true);
117+
118+
public void IncludeInheritedMethodAttributes()
119+
=> this.MethodAttributes = Reflection.GetCustomAttributes(this.Method, true);
115120

116121
protected virtual object ConvertMethodResult(object convertibleMethodResult) => convertibleMethodResult;
117122
}

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

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
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, object> TypeAttributesCache = new ConcurrentDictionary<Type, object>();
22-
private static readonly ConcurrentDictionary<MethodInfo, IEnumerable<object>> MethodAttributesCache = new ConcurrentDictionary<MethodInfo, IEnumerable<object>>();
24+
private static readonly ConcurrentDictionary<Type, object> MethodAttributesCache = new ConcurrentDictionary<Type, object>();
2325
private static readonly ConcurrentDictionary<Type, string> FriendlyTypeNames = new ConcurrentDictionary<Type, string>();
2426
private static readonly ConcurrentDictionary<Type, string> FullFriendlyTypeNames = new ConcurrentDictionary<Type, string>();
2527

@@ -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,27 +285,38 @@ 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
{
289-
CacheComponentAttributes(obj);
292+
var type = obj.GetType();
293+
var attributes = type.GetTypeInfo().GetCustomAttributes(shouldInherit);
294+
if (attributes.Length == TypeAttributesCache.Count)
295+
{
296+
return TypeAttributesCache.Values;
297+
}
298+
299+
CacheAttributes(attributes, TypeAttributesCache);
290300
return TypeAttributesCache.Values;
291301
}
292302

293303
/// <summary>
294-
/// Gets custom attributes including inherited ones on the provided object.
304+
/// Gets custom attributes on the provided method.
295305
/// </summary>
296-
/// <param name="obj">Object decorated with custom attribute.</param>
306+
/// <param name="method">Method decorated with custom attribute.</param>
307+
/// <param name="shouldInherit">Indicates whether it should include inherited method attributes</param>
297308
/// <returns>IEnumerable of objects representing the custom attributes.</returns>
298-
public static IEnumerable<object> GetCustomAttributesIncludingInherited(object obj)
309+
public static IEnumerable<object> GetCustomAttributes(MethodInfo method, bool shouldInherit = ShouldInheritAttributes)
299310
{
300-
CacheComponentAttributes(obj, true);
301-
return TypeAttributesCache.Values;
302-
}
311+
var attributes = method.GetCustomAttributes(shouldInherit);
312+
if (attributes.Length == MethodAttributesCache.Count)
313+
{
314+
return MethodAttributesCache.Values;
315+
}
303316

304-
public static IEnumerable<object> GetCustomAttributes(MethodInfo method)
305-
=> MethodAttributesCache
306-
.GetOrAdd(method, _ => method.GetCustomAttributes(false));
317+
CacheAttributes(attributes, MethodAttributesCache);
318+
return MethodAttributesCache.Values;
319+
}
307320

308321
/// <summary>
309322
/// 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.
@@ -398,7 +411,7 @@ public static TDelegate CreateDelegateFromMethod<TDelegate>(object instance, Fun
398411
.FirstOrDefault(methodFilter)
399412
?.CreateDelegate(typeof(TDelegate), instance) as TDelegate;
400413
}
401-
414+
402415
/// <summary>
403416
/// Checks whether property with the provided name exists in a dynamic object.
404417
/// </summary>
@@ -472,8 +485,8 @@ private static ConstructorInfo GetConstructorByUnorderedParameters(this Type typ
472485
}
473486

474487
private static bool AreDeeplyEqual(
475-
object expected,
476-
object actual,
488+
object expected,
489+
object actual,
477490
ConditionalWeakTable<object, object> processedElements,
478491
DeepEqualityResult result)
479492
{
@@ -541,7 +554,7 @@ private static bool AreDeeplyEqual(
541554
? result.Success
542555
: result.Failure;
543556
}
544-
557+
545558
var equalsOperator = expectedType.GetMethods().FirstOrDefault(m => m.Name == "op_Equality");
546559
if (equalsOperator != null)
547560
{
@@ -593,15 +606,15 @@ private static bool AreDeeplyEqual(
593606
}
594607

595608
private static bool AreNotDeeplyEqual(
596-
object expected,
597-
object actual,
609+
object expected,
610+
object actual,
598611
ConditionalWeakTable<object, object> processedElements,
599612
DeepEqualityResult result)
600613
=> !AreDeeplyEqual(expected, actual, processedElements, result);
601614

602615
private static bool CollectionsAreDeeplyEqual(
603-
object expected,
604-
object actual,
616+
object expected,
617+
object actual,
605618
ConditionalWeakTable<object, object> processedElements,
606619
DeepEqualityResult result)
607620
{
@@ -685,8 +698,8 @@ private static bool ObjectImplementsIComparable(object obj)
685698
.FirstOrDefault(i => i.Name.StartsWith("IComparable")) != null;
686699

687700
private static bool ObjectPropertiesAreDeeplyEqual(
688-
object expected,
689-
object actual,
701+
object expected,
702+
object actual,
690703
ConditionalWeakTable<object, object> processedElements,
691704
DeepEqualityResult result)
692705
{
@@ -706,8 +719,8 @@ private static bool ObjectPropertiesAreDeeplyEqual(
706719
if (expectedPropertyValue is IEnumerable && expectedPropertyValue.GetType() != typeof(string))
707720
{
708721
if (!CollectionsAreDeeplyEqual(
709-
expectedPropertyValue,
710-
actualPropertyValue,
722+
expectedPropertyValue,
723+
actualPropertyValue,
711724
processedElements,
712725
result))
713726
{
@@ -716,8 +729,8 @@ private static bool ObjectPropertiesAreDeeplyEqual(
716729
}
717730

718731
var propertiesAreDifferent = AreNotDeeplyEqual(
719-
expectedPropertyValue,
720-
actualPropertyValue,
732+
expectedPropertyValue,
733+
actualPropertyValue,
721734
processedElements,
722735
result);
723736

@@ -732,16 +745,14 @@ private static bool ObjectPropertiesAreDeeplyEqual(
732745
return result.Success;
733746
}
734747

735-
private static void CacheComponentAttributes(object obj, bool shouldInherit = false)
748+
private static void CacheAttributes(IEnumerable<object> attributes, ConcurrentDictionary<Type, object> result)
736749
{
737-
var type = obj.GetType();
738-
var attributes = type.GetTypeInfo().GetCustomAttributes(shouldInherit);
739750
foreach (var attribute in attributes)
740751
{
741752
var attributeType = attribute.GetType();
742-
if (!TypeAttributesCache.ContainsKey(attributeType))
753+
if (!result.ContainsKey(attributeType))
743754
{
744-
TypeAttributesCache.TryAdd(attributeType, attribute);
755+
result.TryAdd(attributeType, attribute);
745756
}
746757
}
747758
}
@@ -750,7 +761,7 @@ private static string GetFriendlyTypeName(Type type, bool useFullName)
750761
{
751762
const string anonymousTypePrefix = "<>f__";
752763

753-
var typeName = useFullName
764+
var typeName = useFullName
754765
? type?.FullName ?? type?.Name
755766
: type?.Name;
756767

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ public ActionAttributesTestBuilder(ControllerTestContext testContext)
3333
public override void ThrowNewAttributeAssertionException(string expectedValue, string actualValue)
3434
=> throw new AttributeAssertionException($"{this.TestContext.ExceptionMessagePrefix} action to have {expectedValue}, but {actualValue}.");
3535

36+
/// <inheritdoc />
3637
public IAndActionAttributesTestBuilder IncludingInherited()
37-
=> throw new System.NotImplementedException();
38+
{
39+
this.TestContext.IncludeInheritedMethodAttributes();
40+
return this;
41+
}
3842
}
3943
}

0 commit comments

Comments
 (0)