From d3da129545d83ca71dc8547192386e9a2aa4675f Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Tue, 30 Oct 2018 22:28:31 -0400 Subject: [PATCH 1/7] Consider types assignable to open generics (#954) --- Src/FluentAssertions/Types/TypeAssertions.cs | 63 +++++++++- Tests/Shared.Specs/TypeAssertionSpecs.cs | 123 ++++++++++++++++++- 2 files changed, 183 insertions(+), 3 deletions(-) diff --git a/Src/FluentAssertions/Types/TypeAssertions.cs b/Src/FluentAssertions/Types/TypeAssertions.cs index b70c5428c6..d3bf553dae 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -72,6 +72,45 @@ public new AndConstraint BeAssignableTo(string because = "", return BeAssignableTo(typeof(T), because, becauseArgs); } + bool CheckIfAssignableToOpenGeneric(Type definition) + { + // The CLR type system does not consider anything to be assignable to an open generic type. + // For the purposes of test assertions, the user probably means that the subject type is + // assignable to any generic type based on the given generic type definition. + + if (definition.GetTypeInfo().IsInterface) + { + // check subject and its interfaces against definition + TypeInfo subjectInfo = Subject.GetTypeInfo(); + if (subjectInfo.IsInterface && subjectInfo.IsGenericType && + subjectInfo.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) + { + return true; + } + foreach (TypeInfo iface in subjectInfo.ImplementedInterfaces.Select(i => i.GetTypeInfo())) + { + if (iface.IsGenericType && iface.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) + { + return true; + } + } + } + else + { + // check subject and its base types against definition + for (TypeInfo baseType = Subject.GetTypeInfo(); baseType != null; + baseType = baseType.BaseType?.GetTypeInfo()) + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) + { + return true; + } + } + } + + return false; + } + /// /// Asserts than an instance of the subject type is assignable variable of given . /// @@ -81,8 +120,18 @@ public new AndConstraint BeAssignableTo(string because = "", /// An which can be used to chain assertions. public new AndConstraint BeAssignableTo(Type type, string because = "", params object[] becauseArgs) { + bool condition; + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + condition = CheckIfAssignableToOpenGeneric(type); + } + else + { + condition = type.IsAssignableFrom(Subject); + } + Execute.Assertion - .ForCondition(type.IsAssignableFrom(Subject)) + .ForCondition(condition) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:" + Identifier + "} {0} to be assignable to {1}{reason}, but it is not.", @@ -113,8 +162,18 @@ public new AndConstraint NotBeAssignableTo(string because = " /// An which can be used to chain assertions. public new AndConstraint NotBeAssignableTo(Type type, string because = "", params object[] becauseArgs) { + bool condition; + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + condition = !CheckIfAssignableToOpenGeneric(type); + } + else + { + condition = !type.IsAssignableFrom(Subject); + } + Execute.Assertion - .ForCondition(!type.IsAssignableFrom(Subject)) + .ForCondition(condition) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:" + Identifier + "} {0} to not be assignable to {1}{reason}, but it is.", diff --git a/Tests/Shared.Specs/TypeAssertionSpecs.cs b/Tests/Shared.Specs/TypeAssertionSpecs.cs index ed1c78998e..2d42e9355d 100644 --- a/Tests/Shared.Specs/TypeAssertionSpecs.cs +++ b/Tests/Shared.Specs/TypeAssertionSpecs.cs @@ -484,6 +484,65 @@ public void When_an_unrelated_type_instance_it_fails_with_a_useful_message() .WithMessage($"*{typeof(DummyImplementingClass)} to be assignable to {typeof(DateTime)}*failure message*"); } + [Fact] + public void When_constructed_of_open_generic_it_succeeds() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange / Act / Assert + //----------------------------------------------------------------------------------------------------------- + typeof(IDummyInterface).Should().BeAssignableTo(typeof(IDummyInterface<>)); + } + + [Fact] + public void When_implementation_of_open_generic_interface_it_succeeds() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange / Act / Assert + //----------------------------------------------------------------------------------------------------------- + typeof(ClassWithGenericBaseType).Should().BeAssignableTo(typeof(IDummyInterface<>)); + } + + [Fact] + public void When_derived_of_open_generic_class_it_succeeds() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange / Act / Assert + //----------------------------------------------------------------------------------------------------------- + typeof(ClassWithGenericBaseType).Should().BeAssignableTo(typeof(DummyBaseType<>)); + } + + [Fact] + public void When_unrelated_to_open_generic_interface_it_fails_with_a_useful_message() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + Type someType = typeof(IDummyInterface); + Action act = () => someType.Should().BeAssignableTo(typeof(IDummyInterface<>), "because we want to test the failure {0}", "message"); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"*{typeof(IDummyInterface)} to be assignable to {typeof(IDummyInterface<>)}*failure message*"); + } + + [Fact] + public void When_unrelated_to_open_generic_type_it_fails_with_a_useful_message() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + Type someType = typeof(ClassWithAttribute); + Action act = () => someType.Should().BeAssignableTo(typeof(DummyBaseType<>), "because we want to test the failure {0}", "message"); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"*{typeof(ClassWithAttribute)} to be assignable to {typeof(DummyBaseType<>)}*failure message*"); + } + #endregion #region NotBeAssignableTo @@ -596,6 +655,56 @@ public void When_an_unrelated_type_instance_and_asserting_not_assignable_it_succ typeof(DummyImplementingClass).Should().NotBeAssignableTo(typeof(DateTime)); } + [Fact] + public void When_unrelated_to_open_generic_interface_and_asserting_not_assignable_it_succeeds() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange / Act / Assert + //----------------------------------------------------------------------------------------------------------- + typeof(ClassWithAttribute).Should().NotBeAssignableTo(typeof(IDummyInterface<>)); + } + + [Fact] + public void When_unrelated_to_open_generic_class_and_asserting_not_assignable_it_succeeds() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange / Act / Assert + //----------------------------------------------------------------------------------------------------------- + typeof(ClassWithAttribute).Should().NotBeAssignableTo(typeof(DummyBaseType<>)); + } + + [Fact] + public void When_implementation_of_open_generic_interface_and_asserting_not_assignable_it_fails_with_a_useful_message() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + Type someType = typeof(ClassWithGenericBaseType); + Action act = () => someType.Should().NotBeAssignableTo(typeof(IDummyInterface<>), "because we want to test the failure {0}", "message"); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"*{typeof(ClassWithGenericBaseType)} to not be assignable to {typeof(IDummyInterface<>)}*failure message*"); + } + + [Fact] + public void When_derived_from_open_generic_class_and_asserting_not_assignable_it_fails_with_a_useful_message() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + Type someType = typeof(ClassWithGenericBaseType); + Action act = () => someType.Should().NotBeAssignableTo(typeof(IDummyInterface<>), "because we want to test the failure {0}", "message"); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"*{typeof(ClassWithGenericBaseType)} to not be assignable to {typeof(IDummyInterface<>)}*failure message*"); + } + #endregion #region BeDerivedFrom @@ -4508,7 +4617,11 @@ public interface IDummyInterface { } - public class ClassThatImplementsInterface : IDummyInterface + public interface IDummyInterface + { + } + + public class ClassThatImplementsInterface : IDummyInterface, IDummyInterface { } @@ -4516,6 +4629,14 @@ public class ClassThatDoesNotImplementInterface { } + public class DummyBaseType : IDummyInterface + { + } + + public class ClassWithGenericBaseType : DummyBaseType + { + } + public class ClassWithMembers { protected internal ClassWithMembers() { } From 10e7b3d456019186ab55c966d5772381f6cb73f4 Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Wed, 31 Oct 2018 18:43:44 -0400 Subject: [PATCH 2/7] code clean up --- Src/FluentAssertions/Types/TypeAssertions.cs | 104 ++++++++++--------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/Src/FluentAssertions/Types/TypeAssertions.cs b/Src/FluentAssertions/Types/TypeAssertions.cs index d3bf553dae..77a4a34993 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -72,45 +72,6 @@ public new AndConstraint BeAssignableTo(string because = "", return BeAssignableTo(typeof(T), because, becauseArgs); } - bool CheckIfAssignableToOpenGeneric(Type definition) - { - // The CLR type system does not consider anything to be assignable to an open generic type. - // For the purposes of test assertions, the user probably means that the subject type is - // assignable to any generic type based on the given generic type definition. - - if (definition.GetTypeInfo().IsInterface) - { - // check subject and its interfaces against definition - TypeInfo subjectInfo = Subject.GetTypeInfo(); - if (subjectInfo.IsInterface && subjectInfo.IsGenericType && - subjectInfo.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) - { - return true; - } - foreach (TypeInfo iface in subjectInfo.ImplementedInterfaces.Select(i => i.GetTypeInfo())) - { - if (iface.IsGenericType && iface.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) - { - return true; - } - } - } - else - { - // check subject and its base types against definition - for (TypeInfo baseType = Subject.GetTypeInfo(); baseType != null; - baseType = baseType.BaseType?.GetTypeInfo()) - { - if (baseType.IsGenericType && baseType.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) - { - return true; - } - } - } - - return false; - } - /// /// Asserts than an instance of the subject type is assignable variable of given . /// @@ -120,18 +81,18 @@ bool CheckIfAssignableToOpenGeneric(Type definition) /// An which can be used to chain assertions. public new AndConstraint BeAssignableTo(Type type, string because = "", params object[] becauseArgs) { - bool condition; + bool isAssignable; if (type.GetTypeInfo().IsGenericTypeDefinition) { - condition = CheckIfAssignableToOpenGeneric(type); + isAssignable = IsAssignableToOpenGeneric(type); } else { - condition = type.IsAssignableFrom(Subject); + isAssignable = type.IsAssignableFrom(Subject); } Execute.Assertion - .ForCondition(condition) + .ForCondition(isAssignable) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:" + Identifier + "} {0} to be assignable to {1}{reason}, but it is not.", @@ -141,6 +102,55 @@ public new AndConstraint BeAssignableTo(Type type, string becaus return new AndConstraint(this); } + bool IsAssignableToOpenGeneric(Type definition) + { + // The CLR type system does not consider anything to be assignable to an open generic type. + // For the purposes of test assertions, the user probably means that the subject type is + // assignable to any generic type based on the given generic type definition. + + if (definition.GetTypeInfo().IsInterface) + { + return IsImplementationOfOpenGeneric(definition); + } + else + { + return IsDerivedFromOpenGeneric(definition); + } + } + + bool IsImplementationOfOpenGeneric(Type definition) + { + // check subject against definition + TypeInfo subjectInfo = Subject.GetTypeInfo(); + if (subjectInfo.IsInterface && subjectInfo.IsGenericType && + subjectInfo.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) + { + return true; + } + + // check subject's interfaces against definition + return subjectInfo.ImplementedInterfaces + .Select(i => i.GetTypeInfo()) + .Where(i => i.IsGenericType) + .Select(i => i.GetGenericTypeDefinition()) + .Any(d => d.IsSameOrEqualTo(definition)); + } + + bool IsDerivedFromOpenGeneric(Type definition) + { + // check subject and its base types against definition + for (TypeInfo baseType = Subject.GetTypeInfo(); baseType != null; + baseType = baseType.BaseType?.GetTypeInfo()) + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) + { + return true; + } + } + + return false; + } + /// /// Asserts than an instance of the subject type is not assignable variable of type . /// @@ -162,18 +172,18 @@ public new AndConstraint NotBeAssignableTo(string because = " /// An which can be used to chain assertions. public new AndConstraint NotBeAssignableTo(Type type, string because = "", params object[] becauseArgs) { - bool condition; + bool isNotAssignable; if (type.GetTypeInfo().IsGenericTypeDefinition) { - condition = !CheckIfAssignableToOpenGeneric(type); + isNotAssignable = !IsAssignableToOpenGeneric(type); } else { - condition = !type.IsAssignableFrom(Subject); + isNotAssignable = !type.IsAssignableFrom(Subject); } Execute.Assertion - .ForCondition(condition) + .ForCondition(isNotAssignable) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:" + Identifier + "} {0} to not be assignable to {1}{reason}, but it is.", From 4672c52ddcdf473e9f59a8ce8b7510641fd611f0 Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Wed, 31 Oct 2018 19:30:17 -0400 Subject: [PATCH 3/7] also consider open generics as base types --- Src/FluentAssertions/Types/TypeAssertions.cs | 32 ++++- Tests/Shared.Specs/TypeAssertionSpecs.cs | 132 +++++++++++++++++++ 2 files changed, 161 insertions(+), 3 deletions(-) diff --git a/Src/FluentAssertions/Types/TypeAssertions.cs b/Src/FluentAssertions/Types/TypeAssertions.cs index 77a4a34993..52647bf5d0 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -114,7 +114,7 @@ bool IsAssignableToOpenGeneric(Type definition) } else { - return IsDerivedFromOpenGeneric(definition); + return Subject.IsSameOrEqualTo(definition) || IsDerivedFromOpenGeneric(definition); } } @@ -138,6 +138,12 @@ bool IsImplementationOfOpenGeneric(Type definition) bool IsDerivedFromOpenGeneric(Type definition) { + if (Subject.IsSameOrEqualTo(definition)) + { + // do not consider a type to be derived from itself + return false; + } + // check subject and its base types against definition for (TypeInfo baseType = Subject.GetTypeInfo(); baseType != null; baseType = baseType.BaseType?.GetTypeInfo()) @@ -540,7 +546,17 @@ public AndConstraint BeDerivedFrom(Type baseType, string because throw new ArgumentException("Must not be an interface Type.", nameof(baseType)); } - Execute.Assertion.ForCondition(Subject.GetTypeInfo().IsSubclassOf(baseType)) + bool isDerivedFrom; + if (baseType.GetTypeInfo().IsGenericTypeDefinition) + { + isDerivedFrom = IsDerivedFromOpenGeneric(baseType); + } + else + { + isDerivedFrom = Subject.GetTypeInfo().IsSubclassOf(baseType); + } + + Execute.Assertion.ForCondition(isDerivedFrom) .BecauseOf(because, becauseArgs) .FailWith("Expected type {0} to be derived from {1}{reason}, but it is not.", Subject, baseType); @@ -574,8 +590,18 @@ public AndConstraint NotBeDerivedFrom(Type baseType, string beca throw new ArgumentException("Must not be an interface Type.", nameof(baseType)); } + bool isNotDerivedFrom; + if (baseType.GetTypeInfo().IsGenericTypeDefinition) + { + isNotDerivedFrom = !IsDerivedFromOpenGeneric(baseType); + } + else + { + isNotDerivedFrom = !Subject.GetTypeInfo().IsSubclassOf(baseType); + } + Execute.Assertion - .ForCondition(!Subject.GetTypeInfo().IsSubclassOf(baseType)) + .ForCondition(isNotDerivedFrom) .BecauseOf(because, becauseArgs) .FailWith("Expected type {0} not to be derived from {1}{reason}, but it is.", Subject, baseType); diff --git a/Tests/Shared.Specs/TypeAssertionSpecs.cs b/Tests/Shared.Specs/TypeAssertionSpecs.cs index 2d42e9355d..dea996d26f 100644 --- a/Tests/Shared.Specs/TypeAssertionSpecs.cs +++ b/Tests/Shared.Specs/TypeAssertionSpecs.cs @@ -542,6 +542,14 @@ public void When_unrelated_to_open_generic_type_it_fails_with_a_useful_message() act.Should().Throw() .WithMessage($"*{typeof(ClassWithAttribute)} to be assignable to {typeof(DummyBaseType<>)}*failure message*"); } + + [Fact] + public void When_asserting_an_open_generic_class_is_assignable_to_itself_it_succeeds() + { + //------------------------------------------------------------------------------------------------------------------- + // Arrange / Act / Assert + //------------------------------------------------------------------------------------------------------------------- + typeof(DummyBaseType<>).Should().BeAssignableTo(typeof(DummyBaseType<>)); } #endregion @@ -772,6 +780,68 @@ public void When_asserting_a_type_is_derived_from_an_interface_it_fails_with_a_u .WithMessage("Must not be an interface Type.*Parameter name: baseType"); } + [Fact] + public void When_asserting_a_type_is_derived_from_an_open_generic_it_succeeds() + { + //------------------------------------------------------------------------------------------------------------------- + // Arrange + //------------------------------------------------------------------------------------------------------------------- + var type = typeof(DummyBaseType); + + //------------------------------------------------------------------------------------------------------------------- + // Act + //------------------------------------------------------------------------------------------------------------------- + Action act = () => + type.Should().BeDerivedFrom(typeof(DummyBaseType<>)); + + //------------------------------------------------------------------------------------------------------------------- + // Assert + //------------------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_type_is_derived_from_an_open_generic_base_class_it_succeeds() + { + //------------------------------------------------------------------------------------------------------------------- + // Arrange + //------------------------------------------------------------------------------------------------------------------- + var type = typeof(ClassWithGenericBaseType); + + //------------------------------------------------------------------------------------------------------------------- + // Act + //------------------------------------------------------------------------------------------------------------------- + Action act = () => + type.Should().BeDerivedFrom(typeof(DummyBaseType<>)); + + //------------------------------------------------------------------------------------------------------------------- + // Assert + //------------------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_type_is_derived_from_an_unrelated_open_generic_class_it_fails_with_a_useful_message() + { + //------------------------------------------------------------------------------------------------------------------- + // Arrange + //------------------------------------------------------------------------------------------------------------------- + var type = typeof(ClassWithMembers); + + //------------------------------------------------------------------------------------------------------------------- + // Act + //------------------------------------------------------------------------------------------------------------------- + Action act = () => + type.Should().BeDerivedFrom(typeof(DummyBaseType<>), "because we want to test the error {0}", "message"); + + //------------------------------------------------------------------------------------------------------------------- + // Assert + //------------------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"Expected type {typeof(ClassWithMembers)} to be derived from " + + $"{typeof(DummyBaseType<>)} because we want to test the error message, but it is not."); + } + #endregion #region BeDerivedFromOfT @@ -863,6 +933,68 @@ public void When_asserting_a_type_is_not_derived_from_an_interface_it_fails_with .WithMessage("Must not be an interface Type.*Parameter name: baseType"); } + [Fact] + public void When_asserting_a_type_is_not_derived_from_an_unrelated_open_generic_it_succeeds() + { + //------------------------------------------------------------------------------------------------------------------- + // Arrange + //------------------------------------------------------------------------------------------------------------------- + var type = typeof(ClassWithMembers); + + //------------------------------------------------------------------------------------------------------------------- + // Act + //------------------------------------------------------------------------------------------------------------------- + Action act = () => + type.Should().NotBeDerivedFrom(typeof(DummyBaseType<>)); + + //------------------------------------------------------------------------------------------------------------------- + // Assert + //------------------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_an_open_generic_type_is_not_derived_from_itself_it_succeeds() + { + //------------------------------------------------------------------------------------------------------------------- + // Arrange + //------------------------------------------------------------------------------------------------------------------- + var type = typeof(DummyBaseType<>); + + //------------------------------------------------------------------------------------------------------------------- + // Act + //------------------------------------------------------------------------------------------------------------------- + Action act = () => + type.Should().NotBeDerivedFrom(typeof(DummyBaseType<>)); + + //------------------------------------------------------------------------------------------------------------------- + // Assert + //------------------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_type_is_not_derived_from_its_open_generic_it_fails_with_a_useful_message() + { + //------------------------------------------------------------------------------------------------------------------- + // Arrange + //------------------------------------------------------------------------------------------------------------------- + var type = typeof(DummyBaseType); + + //------------------------------------------------------------------------------------------------------------------- + // Act + //------------------------------------------------------------------------------------------------------------------- + Action act = () => + type.Should().NotBeDerivedFrom(typeof(DummyBaseType<>), "because we want to test the error {0}", "message"); + + //------------------------------------------------------------------------------------------------------------------- + // Assert + //------------------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"Expected type*{typeof(DummyBaseType)}*not to be derived from*" + + $"{typeof(DummyBaseType<>)}*because we want to test the error message*but it is."); + } + #endregion #region NotBeDerivedFromOfT From d5da03083b25a4993be3bbb44a712d576607b04f Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Thu, 1 Nov 2018 22:24:51 -0400 Subject: [PATCH 4/7] code clean up --- Src/FluentAssertions/Types/TypeAssertions.cs | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Src/FluentAssertions/Types/TypeAssertions.cs b/Src/FluentAssertions/Types/TypeAssertions.cs index 52647bf5d0..07c0d6c96d 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -102,7 +102,7 @@ public new AndConstraint BeAssignableTo(Type type, string becaus return new AndConstraint(this); } - bool IsAssignableToOpenGeneric(Type definition) + private bool IsAssignableToOpenGeneric(Type definition) { // The CLR type system does not consider anything to be assignable to an open generic type. // For the purposes of test assertions, the user probably means that the subject type is @@ -118,7 +118,7 @@ bool IsAssignableToOpenGeneric(Type definition) } } - bool IsImplementationOfOpenGeneric(Type definition) + private bool IsImplementationOfOpenGeneric(Type definition) { // check subject against definition TypeInfo subjectInfo = Subject.GetTypeInfo(); @@ -136,7 +136,7 @@ bool IsImplementationOfOpenGeneric(Type definition) .Any(d => d.IsSameOrEqualTo(definition)); } - bool IsDerivedFromOpenGeneric(Type definition) + private bool IsDerivedFromOpenGeneric(Type definition) { if (Subject.IsSameOrEqualTo(definition)) { @@ -178,18 +178,18 @@ public new AndConstraint NotBeAssignableTo(string because = " /// An which can be used to chain assertions. public new AndConstraint NotBeAssignableTo(Type type, string because = "", params object[] becauseArgs) { - bool isNotAssignable; + bool isAssignable; if (type.GetTypeInfo().IsGenericTypeDefinition) { - isNotAssignable = !IsAssignableToOpenGeneric(type); + isAssignable = IsAssignableToOpenGeneric(type); } else { - isNotAssignable = !type.IsAssignableFrom(Subject); + isAssignable = type.IsAssignableFrom(Subject); } Execute.Assertion - .ForCondition(isNotAssignable) + .ForCondition(!isAssignable) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:" + Identifier + "} {0} to not be assignable to {1}{reason}, but it is.", @@ -590,18 +590,18 @@ public AndConstraint NotBeDerivedFrom(Type baseType, string beca throw new ArgumentException("Must not be an interface Type.", nameof(baseType)); } - bool isNotDerivedFrom; + bool isDerivedFrom; if (baseType.GetTypeInfo().IsGenericTypeDefinition) { - isNotDerivedFrom = !IsDerivedFromOpenGeneric(baseType); + isDerivedFrom = IsDerivedFromOpenGeneric(baseType); } else { - isNotDerivedFrom = !Subject.GetTypeInfo().IsSubclassOf(baseType); + isDerivedFrom = Subject.GetTypeInfo().IsSubclassOf(baseType); } Execute.Assertion - .ForCondition(isNotDerivedFrom) + .ForCondition(!isDerivedFrom) .BecauseOf(because, becauseArgs) .FailWith("Expected type {0} not to be derived from {1}{reason}, but it is.", Subject, baseType); From 8ced7986853064631f0aa04e20f05d374e142a7e Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Sat, 3 Nov 2018 12:46:01 -0400 Subject: [PATCH 5/7] make BeOfType NotBeOfType consistent --- .../Primitives/ReferenceTypeAssertions.cs | 10 ++++++- .../ReferenceTypeAssertionsSpecs.cs | 26 ++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs index 1eb0780354..a717e1d36c 100644 --- a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs +++ b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs @@ -198,7 +198,15 @@ public AndConstraint NotBeOfType(Type expectedType, string because .WithDefaultIdentifier("type") .FailWith("Expected {context} not to be {0}{reason}, but found .", expectedType); - Subject.GetType().Should().NotBe(expectedType, because, becauseArgs); + Type subjectType = Subject.GetType(); + if (expectedType.GetTypeInfo().IsGenericTypeDefinition && subjectType.GetTypeInfo().IsGenericType) + { + subjectType.GetGenericTypeDefinition().Should().NotBe(expectedType, because, becauseArgs); + } + else + { + subjectType.Should().NotBe(expectedType, because, becauseArgs); + } return new AndConstraint((TAssertions)this); } diff --git a/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs b/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs index 329ccd8e72..65cbdbbea9 100644 --- a/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs +++ b/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs @@ -118,17 +118,17 @@ public void When_object_is_of_the_expected_type_it_should_not_throw() } [Fact] - public void When_object_is_an_open_generic_of_the_expected_type_it_should_not_throw() + public void When_object_is_of_the_expected_open_generic_type_it_should_not_throw() { //----------------------------------------------------------------------------------------------------------- // Arrange //----------------------------------------------------------------------------------------------------------- - var openGeneric = new System.Collections.Generic.List(); + var aList = new System.Collections.Generic.List(); //----------------------------------------------------------------------------------------------------------- // Act //----------------------------------------------------------------------------------------------------------- - Action action = () => openGeneric.Should().BeOfType(typeof(System.Collections.Generic.List<>)); + Action action = () => aList.Should().BeOfType(typeof(System.Collections.Generic.List<>)); //----------------------------------------------------------------------------------------------------------- // Assert @@ -220,6 +220,26 @@ public void When_object_is_of_the_unexpected_generic_type_it_should_throw() .WithMessage("Expected type not to be [" + typeof(string).AssemblyQualifiedName + "], but it is."); } + [Fact] + public void When_object_is_of_the_unexpected_open_generic_type_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var aList = new System.Collections.Generic.List(); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => aList.Should().NotBeOfType(typeof(System.Collections.Generic.List<>)); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().Throw() + .WithMessage("Expected type not to be [" + typeof(System.Collections.Generic.List<>).AssemblyQualifiedName + "], but it is."); + } + [Fact] public void When_object_is_not_of_the_expected_type_it_should_not_throw() { From 93a5c704cb0b613c85cd6805ce917744b1a3ddad Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Sat, 3 Nov 2018 13:05:58 -0400 Subject: [PATCH 6/7] make BeAssignableTo same for objects --- Src/FluentAssertions/Common/TypeExtensions.cs | 55 ++++++++++++++++ .../Primitives/ReferenceTypeAssertions.cs | 25 +++++++- Src/FluentAssertions/Types/TypeAssertions.cs | 63 ++----------------- Tests/Shared.Specs/ObjectAssertionSpecs.cs | 30 +++++++++ 4 files changed, 112 insertions(+), 61 deletions(-) diff --git a/Src/FluentAssertions/Common/TypeExtensions.cs b/Src/FluentAssertions/Common/TypeExtensions.cs index befd8703d1..dbfde782e3 100644 --- a/Src/FluentAssertions/Common/TypeExtensions.cs +++ b/Src/FluentAssertions/Common/TypeExtensions.cs @@ -455,5 +455,60 @@ private static bool IsTuple(this Type type) || openType == typeof(ValueTuple<,,,,,,>) || (openType == typeof(ValueTuple<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7])); } + + internal static bool IsAssignableToOpenGeneric(this Type type, Type definition) + { + // The CLR type system does not consider anything to be assignable to an open generic type. + // For the purposes of test assertions, the user probably means that the subject type is + // assignable to any generic type based on the given generic type definition. + + if (definition.GetTypeInfo().IsInterface) + { + return type.IsImplementationOfOpenGeneric(definition); + } + else + { + return type.IsSameOrEqualTo(definition) || type.IsDerivedFromOpenGeneric(definition); + } + } + + internal static bool IsImplementationOfOpenGeneric(this Type type, Type definition) + { + // check subject against definition + TypeInfo subjectInfo = type.GetTypeInfo(); + if (subjectInfo.IsInterface && subjectInfo.IsGenericType && + subjectInfo.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) + { + return true; + } + + // check subject's interfaces against definition + return subjectInfo.ImplementedInterfaces + .Select(i => i.GetTypeInfo()) + .Where(i => i.IsGenericType) + .Select(i => i.GetGenericTypeDefinition()) + .Any(d => d.IsSameOrEqualTo(definition)); + } + + internal static bool IsDerivedFromOpenGeneric(this Type type, Type definition) + { + if (type.IsSameOrEqualTo(definition)) + { + // do not consider a type to be derived from itself + return false; + } + + // check subject and its base types against definition + for (TypeInfo baseType = type.GetTypeInfo(); baseType != null; + baseType = baseType.BaseType?.GetTypeInfo()) + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) + { + return true; + } + } + + return false; + } } } diff --git a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs index a717e1d36c..3caccba44d 100644 --- a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs +++ b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs @@ -3,6 +3,7 @@ using System.Linq.Expressions; using System.Reflection; +using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Primitives @@ -246,8 +247,18 @@ public AndConstraint BeAssignableTo(Type type, string because = "", .WithDefaultIdentifier("type") .FailWith("Expected {context} not to be {0}{reason}, but found .", type); + bool isAssignable; + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + isAssignable = Subject.GetType().IsAssignableToOpenGeneric(type); + } + else + { + isAssignable = type.IsAssignableFrom(Subject.GetType()); + } + Execute.Assertion - .ForCondition(type.IsAssignableFrom(Subject.GetType())) + .ForCondition(isAssignable) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) .FailWith("Expected {context} to be assignable to {0}{reason}, but {1} is not.", @@ -284,8 +295,18 @@ public AndConstraint NotBeAssignableTo(Type type, string because = .WithDefaultIdentifier("type") .FailWith("Expected {context} not to be {0}{reason}, but found .", type); + bool isAssignable; + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + isAssignable = Subject.GetType().IsAssignableToOpenGeneric(type); + } + else + { + isAssignable = type.IsAssignableFrom(Subject.GetType()); + } + Execute.Assertion - .ForCondition(!type.IsAssignableFrom(Subject.GetType())) + .ForCondition(!isAssignable) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) .FailWith("Expected {context} to not be assignable to {0}{reason}, but {1} is.", diff --git a/Src/FluentAssertions/Types/TypeAssertions.cs b/Src/FluentAssertions/Types/TypeAssertions.cs index 07c0d6c96d..de268846f4 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -84,7 +84,7 @@ public new AndConstraint BeAssignableTo(Type type, string becaus bool isAssignable; if (type.GetTypeInfo().IsGenericTypeDefinition) { - isAssignable = IsAssignableToOpenGeneric(type); + isAssignable = Subject.IsAssignableToOpenGeneric(type); } else { @@ -102,61 +102,6 @@ public new AndConstraint BeAssignableTo(Type type, string becaus return new AndConstraint(this); } - private bool IsAssignableToOpenGeneric(Type definition) - { - // The CLR type system does not consider anything to be assignable to an open generic type. - // For the purposes of test assertions, the user probably means that the subject type is - // assignable to any generic type based on the given generic type definition. - - if (definition.GetTypeInfo().IsInterface) - { - return IsImplementationOfOpenGeneric(definition); - } - else - { - return Subject.IsSameOrEqualTo(definition) || IsDerivedFromOpenGeneric(definition); - } - } - - private bool IsImplementationOfOpenGeneric(Type definition) - { - // check subject against definition - TypeInfo subjectInfo = Subject.GetTypeInfo(); - if (subjectInfo.IsInterface && subjectInfo.IsGenericType && - subjectInfo.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) - { - return true; - } - - // check subject's interfaces against definition - return subjectInfo.ImplementedInterfaces - .Select(i => i.GetTypeInfo()) - .Where(i => i.IsGenericType) - .Select(i => i.GetGenericTypeDefinition()) - .Any(d => d.IsSameOrEqualTo(definition)); - } - - private bool IsDerivedFromOpenGeneric(Type definition) - { - if (Subject.IsSameOrEqualTo(definition)) - { - // do not consider a type to be derived from itself - return false; - } - - // check subject and its base types against definition - for (TypeInfo baseType = Subject.GetTypeInfo(); baseType != null; - baseType = baseType.BaseType?.GetTypeInfo()) - { - if (baseType.IsGenericType && baseType.GetGenericTypeDefinition().IsSameOrEqualTo(definition)) - { - return true; - } - } - - return false; - } - /// /// Asserts than an instance of the subject type is not assignable variable of type . /// @@ -181,7 +126,7 @@ public new AndConstraint NotBeAssignableTo(Type type, string bec bool isAssignable; if (type.GetTypeInfo().IsGenericTypeDefinition) { - isAssignable = IsAssignableToOpenGeneric(type); + isAssignable = Subject.IsAssignableToOpenGeneric(type); } else { @@ -549,7 +494,7 @@ public AndConstraint BeDerivedFrom(Type baseType, string because bool isDerivedFrom; if (baseType.GetTypeInfo().IsGenericTypeDefinition) { - isDerivedFrom = IsDerivedFromOpenGeneric(baseType); + isDerivedFrom = Subject.IsDerivedFromOpenGeneric(baseType); } else { @@ -593,7 +538,7 @@ public AndConstraint NotBeDerivedFrom(Type baseType, string beca bool isDerivedFrom; if (baseType.GetTypeInfo().IsGenericTypeDefinition) { - isDerivedFrom = IsDerivedFromOpenGeneric(baseType); + isDerivedFrom = Subject.IsDerivedFromOpenGeneric(baseType); } else { diff --git a/Tests/Shared.Specs/ObjectAssertionSpecs.cs b/Tests/Shared.Specs/ObjectAssertionSpecs.cs index 855478f057..9e69fd9d8a 100644 --- a/Tests/Shared.Specs/ObjectAssertionSpecs.cs +++ b/Tests/Shared.Specs/ObjectAssertionSpecs.cs @@ -646,6 +646,20 @@ public void When_an_implemented_interface_type_instance_it_should_succeed() someObject.Should().BeAssignableTo(typeof(IDisposable)); } + [Fact] + public void When_an_implemented_open_generic_interface_type_instance_it_should_succeed() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var someObject = new System.Collections.Generic.List(); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + someObject.Should().BeAssignableTo(typeof(System.Collections.Generic.IList<>)); + } + [Fact] public void When_an_unrelated_type_instance_it_should_fail_with_a_descriptive_message() { @@ -797,6 +811,22 @@ public void When_an_implemented_interface_type_instance_and_asserting_not_assign .WithMessage($"*not be assignable to {typeof(IDisposable)}*failure message*{typeof(DummyImplementingClass)} is*"); } + [Fact] + public void When_an_implemented_open_generic_interface_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var someObject = new System.Collections.Generic.List(); + Action act = () => someObject.Should().NotBeAssignableTo(typeof(System.Collections.Generic.IList<>), "because we want to test the failure {0}", "message"); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"*not be assignable to {typeof(System.Collections.Generic.IList<>)}*failure message*{typeof(System.Collections.Generic.List)} is*"); + } + [Fact] public void When_an_unrelated_type_instance_and_asserting_not_assignable_it_should_succeed() { From 28c277e3f07a639a7af9fc1e17da176e00a450d6 Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Wed, 7 Nov 2018 23:10:22 -0500 Subject: [PATCH 7/7] add more tests --- Tests/Shared.Specs/ObjectAssertionSpecs.cs | 30 ++++++++++++++ .../ReferenceTypeAssertionsSpecs.cs | 39 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/Tests/Shared.Specs/ObjectAssertionSpecs.cs b/Tests/Shared.Specs/ObjectAssertionSpecs.cs index 9e69fd9d8a..7e1a64c586 100644 --- a/Tests/Shared.Specs/ObjectAssertionSpecs.cs +++ b/Tests/Shared.Specs/ObjectAssertionSpecs.cs @@ -676,6 +676,22 @@ public void When_an_unrelated_type_instance_it_should_fail_with_a_descriptive_me .WithMessage($"*assignable to {typeof(DateTime)}*failure message*{typeof(DummyImplementingClass)} is not*"); } + [Fact] + public void When_unrelated_to_open_generic_type_it_should_fail_with_a_descriptive_message() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var someObject = new DummyImplementingClass(); + Action act = () => someObject.Should().BeAssignableTo(typeof(System.Collections.Generic.IList<>), "because we want to test the failure {0}", "message"); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"*assignable to {typeof(System.Collections.Generic.IList<>)}*failure message*{typeof(DummyImplementingClass)} is not*"); + } + #endregion #region NotBeAssignableTo @@ -841,6 +857,20 @@ public void When_an_unrelated_type_instance_and_asserting_not_assignable_it_shou someObject.Should().NotBeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); } + [Fact] + public void When_unrelated_to_open_generic_type_and_asserting_not_assignable_it_should_succeed() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var someObject = new DummyImplementingClass(); + + //----------------------------------------------------------------------------------------------------------- + // Act / Assert + //----------------------------------------------------------------------------------------------------------- + someObject.Should().NotBeAssignableTo(typeof(System.Collections.Generic.IList<>), "because we want to test the failure {0}", "message"); + } + #endregion #region HaveFlag / NotHaveFlag diff --git a/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs b/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs index 65cbdbbea9..d92370443e 100644 --- a/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs +++ b/Tests/Shared.Specs/ReferenceTypeAssertionsSpecs.cs @@ -136,6 +136,26 @@ public void When_object_is_of_the_expected_open_generic_type_it_should_not_throw action.Should().NotThrow(); } + [Fact] + public void When_object_is_not_of_the_expected_open_generic_type_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var aList = new System.Collections.Generic.List(); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => aList.Should().BeOfType(typeof(System.Collections.Generic.Dictionary<,>)); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().Throw() + .WithMessage($"Expected type to be {typeof(System.Collections.Generic.Dictionary<,>).FullName}, but found {typeof(System.Collections.Generic.List<>).FullName}."); + } + [Fact] public void When_object_is_null_it_should_throw() { @@ -259,6 +279,25 @@ public void When_object_is_not_of_the_expected_type_it_should_not_throw() action.Should().NotThrow(); } + [Fact] + public void When_object_is_not_of_the_unexpected_open_generic_type_it_should_not_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var aList = new System.Collections.Generic.List(); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action action = () => aList.Should().NotBeOfType(typeof(System.Collections.Generic.Dictionary<,>)); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + action.Should().NotThrow(); + } + [Fact] public void When_asserting_object_is_not_of_type_and_it_is_null_it_should_throw() {