From a9abfa8c00d236d61255e3d2a21abc42e0261e0a Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Thu, 8 Nov 2018 08:15:22 -0500 Subject: [PATCH] Consider types assignable to open generics (#954) (#955) * Consider types assignable to open generics (#954) * code clean up * also consider open generics as base types * code clean up * make BeOfType NotBeOfType consistent * make BeAssignableTo same for objects * add more tests --- Src/FluentAssertions/Common/TypeExtensions.cs | 55 ++++ .../Primitives/ReferenceTypeAssertions.cs | 35 ++- Src/FluentAssertions/Types/TypeAssertions.cs | 48 +++- Tests/Shared.Specs/ObjectAssertionSpecs.cs | 60 +++++ .../ReferenceTypeAssertionsSpecs.cs | 65 ++++- Tests/Shared.Specs/TypeAssertionSpecs.cs | 255 +++++++++++++++++- 6 files changed, 507 insertions(+), 11 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 1eb0780354..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 @@ -198,7 +199,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); } @@ -238,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.", @@ -276,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 b70c5428c6..de268846f4 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -81,8 +81,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 isAssignable; + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + isAssignable = Subject.IsAssignableToOpenGeneric(type); + } + else + { + isAssignable = type.IsAssignableFrom(Subject); + } + Execute.Assertion - .ForCondition(type.IsAssignableFrom(Subject)) + .ForCondition(isAssignable) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:" + Identifier + "} {0} to be assignable to {1}{reason}, but it is not.", @@ -113,8 +123,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 isAssignable; + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + isAssignable = Subject.IsAssignableToOpenGeneric(type); + } + else + { + isAssignable = type.IsAssignableFrom(Subject); + } + Execute.Assertion - .ForCondition(!type.IsAssignableFrom(Subject)) + .ForCondition(!isAssignable) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:" + Identifier + "} {0} to not be assignable to {1}{reason}, but it is.", @@ -471,7 +491,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 = Subject.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); @@ -505,8 +535,18 @@ public AndConstraint NotBeDerivedFrom(Type baseType, string beca throw new ArgumentException("Must not be an interface Type.", nameof(baseType)); } + bool isDerivedFrom; + if (baseType.GetTypeInfo().IsGenericTypeDefinition) + { + isDerivedFrom = Subject.IsDerivedFromOpenGeneric(baseType); + } + else + { + isDerivedFrom = Subject.GetTypeInfo().IsSubclassOf(baseType); + } + Execute.Assertion - .ForCondition(!Subject.GetTypeInfo().IsSubclassOf(baseType)) + .ForCondition(!isDerivedFrom) .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/ObjectAssertionSpecs.cs b/Tests/Shared.Specs/ObjectAssertionSpecs.cs index 855478f057..7e1a64c586 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() { @@ -662,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 @@ -797,6 +827,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() { @@ -811,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 329ccd8e72..d92370443e 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 @@ -136,6 +136,26 @@ public void When_object_is_an_open_generic_of_the_expected_type_it_should_not_th 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() { @@ -220,6 +240,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() { @@ -239,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() { diff --git a/Tests/Shared.Specs/TypeAssertionSpecs.cs b/Tests/Shared.Specs/TypeAssertionSpecs.cs index ed1c78998e..dea996d26f 100644 --- a/Tests/Shared.Specs/TypeAssertionSpecs.cs +++ b/Tests/Shared.Specs/TypeAssertionSpecs.cs @@ -484,6 +484,73 @@ 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*"); + } + + [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 #region NotBeAssignableTo @@ -596,6 +663,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 @@ -663,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 @@ -754,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 @@ -4508,7 +4749,11 @@ public interface IDummyInterface { } - public class ClassThatImplementsInterface : IDummyInterface + public interface IDummyInterface + { + } + + public class ClassThatImplementsInterface : IDummyInterface, IDummyInterface { } @@ -4516,6 +4761,14 @@ public class ClassThatDoesNotImplementInterface { } + public class DummyBaseType : IDummyInterface + { + } + + public class ClassWithGenericBaseType : DummyBaseType + { + } + public class ClassWithMembers { protected internal ClassWithMembers() { }