From 361472303eb6a49c04172c33f403d4c2c804da3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Mon, 7 Mar 2022 22:50:08 +0100 Subject: [PATCH 1/9] Improve error message --- Src/FluentAssertions/Xml/XDocumentAssertions.cs | 8 ++++---- .../FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Src/FluentAssertions/Xml/XDocumentAssertions.cs b/Src/FluentAssertions/Xml/XDocumentAssertions.cs index 4d2e376b28..dd41a59357 100644 --- a/Src/FluentAssertions/Xml/XDocumentAssertions.cs +++ b/Src/FluentAssertions/Xml/XDocumentAssertions.cs @@ -132,7 +132,7 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), - "Cannot assert the document has a root element if the element name is *"); + "Cannot assert the document has a root element if the expected name is *"); return HaveRoot(XNamespace.None + expected, because, becauseArgs); } @@ -157,7 +157,7 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected } Guard.ThrowIfArgumentIsNull(expected, nameof(expected), - "Cannot assert the document has a root element if the element name is *"); + "Cannot assert the document has a root element if the expected name is *"); XElement root = Subject.Root; @@ -189,7 +189,7 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), - "Cannot assert the document has an element if the element name is *"); + "Cannot assert the document has an element if the expected name is *"); return HaveElement(XNamespace.None + expected, because, becauseArgs); } @@ -217,7 +217,7 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected } Guard.ThrowIfArgumentIsNull(expected, nameof(expected), - "Cannot assert the document has an element if the element name is *"); + "Cannot assert the document has an element if the expected name is *"); Execute.Assertion .ForCondition(Subject.Root is not null) diff --git a/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs index 050033dbf0..29201cdd2e 100644 --- a/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs @@ -1,5 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System; using System.Xml.Linq; using FluentAssertions.Formatting; using Xunit; @@ -888,7 +887,7 @@ public void When_asserting_a_document_has_a_null_root_element_it_should_fail() // Assert act.Should().Throw().WithMessage( - "Cannot assert the document has a root element if the element name is *"); + "Cannot assert the document has a root element if the expected name is *"); } [Fact] @@ -1107,7 +1106,7 @@ public void When_asserting_a_document_has_a_null_element_it_should_fail() // Assert act.Should().Throw().WithMessage( - "Cannot assert the document has an element if the element name is *"); + "Cannot assert the document has an element if the expected name is *"); } #endregion From d91a3e57bfb08b8eaae80512e50dd043c877cdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Mon, 7 Mar 2022 22:50:57 +0100 Subject: [PATCH 2/9] Improve XML code coverage --- Src/FluentAssertions/Xml/XmlNodeFormatter.cs | 3 +- .../Xml/XDocumentAssertionSpecs.cs | 54 +++++++++++++++++-- .../Xml/XmlNodeFormatterSpecs.cs | 39 ++++++++++++++ 3 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs diff --git a/Src/FluentAssertions/Xml/XmlNodeFormatter.cs b/Src/FluentAssertions/Xml/XmlNodeFormatter.cs index 26db9e3b4c..98921a0d0c 100644 --- a/Src/FluentAssertions/Xml/XmlNodeFormatter.cs +++ b/Src/FluentAssertions/Xml/XmlNodeFormatter.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Xml; +using System.Xml; using FluentAssertions.Common; using FluentAssertions.Formatting; diff --git a/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs index 29201cdd2e..e7c9767ad4 100644 --- a/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Xml.Linq; using FluentAssertions.Formatting; using Xunit; @@ -874,7 +874,7 @@ public void When_asserting_a_null_document_has_root_element_it_should_fail() } [Fact] - public void When_asserting_a_document_has_a_null_root_element_it_should_fail() + public void When_asserting_a_document_has_a_root_element_with_a_null_name_it_should_fail() { // Arrange var theDocument = XDocument.Parse( @@ -890,6 +890,23 @@ public void When_asserting_a_document_has_a_null_root_element_it_should_fail() "Cannot assert the document has a root element if the expected name is *"); } + [Fact] + public void When_asserting_a_document_has_a_root_element_with_a_null_xname_it_should_fail() + { + // Arrange + var theDocument = XDocument.Parse( + @" + + "); + + // Act + Action act = () => theDocument.Should().HaveRoot((XName)null); + + // Assert + act.Should().Throw().WithMessage( + "Cannot assert the document has a root element if the expected name is *"); + } + [Fact] public void When_asserting_document_has_root_element_with_ns_and_it_does_it_should_succeed() { @@ -1093,7 +1110,21 @@ public void When_asserting_a_null_document_has_an_element_it_should_fail() } [Fact] - public void When_asserting_a_document_has_a_null_element_it_should_fail() + public void When_asserting_a_document_without_root_element_has_an_element_it_should_fail() + { + // Arrange + XDocument document = new(); + + // Act + Action act = () => document.Should().HaveElement("unknown"); + + // Assert + act.Should().Throw().WithMessage( + "Expected document to have root element with child \"unknown\", but it has no root element."); + } + + [Fact] + public void When_asserting_a_document_has_an_element_with_a_null_name_it_should_fail() { // Arrange var document = XDocument.Parse( @@ -1109,6 +1140,23 @@ public void When_asserting_a_document_has_a_null_element_it_should_fail() "Cannot assert the document has an element if the expected name is *"); } + [Fact] + public void When_asserting_a_document_has_an_element_with_a_null_xname_it_should_fail() + { + // Arrange + var document = XDocument.Parse( + @" + + "); + + // Act + Action act = () => document.Should().HaveElement((XName)null); + + // Assert + act.Should().ThrowExactly().WithMessage( + "Cannot assert the document has an element if the expected name is *"); + } + #endregion } } diff --git a/Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs new file mode 100644 index 0000000000..10bb78939e --- /dev/null +++ b/Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs @@ -0,0 +1,39 @@ +using System; +using System.Xml; + +using FluentAssertions.Formatting; +using Xunit; + +namespace FluentAssertions.Specs.Xml +{ + public class XmlNodeFormatterSpecs + { + [Fact] + public void When_a_node_is_20_chars_long_it_should_not_be_trimmed() + { + // Arrange + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(@""); + + // Act + string result = Formatter.ToString(xmlDoc); + + // Assert + result.Should().Be(@"" + Environment.NewLine); + } + + [Fact] + public void When_a_node_is_longer_then_20_chars_it_should_be_trimmed() + { + // Arrange + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(@""); + + // Act + string result = Formatter.ToString(xmlDoc); + + // Assert + result.Should().Be(@" Date: Mon, 7 Mar 2022 22:51:18 +0100 Subject: [PATCH 3/9] Improve `Invoking` test coverage --- .../Exceptions/ExceptionAssertionSpecs.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs index 8562dc40e2..4b4c577112 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs @@ -896,6 +896,20 @@ public void When_invoking_a_function_on_a_null_subject_it_should_throw() .WithParameterName("subject"); } + [Fact] + public void When_invoking_a_function_with_null_it_should_throw() + { + // Arrange + object someClass = new(); + + // Act + Action act = () => someClass.Invoking((Func)null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("action"); + } + [Fact] public void When_invoking_an_action_on_a_null_subject_it_should_throw() { @@ -911,7 +925,7 @@ public void When_invoking_an_action_on_a_null_subject_it_should_throw() } [Fact] - public void When_invoking_null_it_should_throw() + public void When_invoking_an_action_with_null_it_should_throw() { // Arrange Does someClass = Does.NotThrow(); From 58dca7ef7fde63b084c4e1c3fbb3826a821d7981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Tue, 8 Mar 2022 22:10:53 +0100 Subject: [PATCH 4/9] Split exception specs in multiple files --- .../Exceptions/ExceptionAssertionSpecs.cs | 990 +----------------- .../Exceptions/InnerExceptionSpecs.cs | 284 +++++ .../Exceptions/InvokingActionSpecs.cs | 36 + .../Exceptions/InvokingFunctionSpecs.cs | 36 + .../Exceptions/MiscellaneousExceptionSpecs.cs | 185 ++++ .../Exceptions/NotThrowSpecs.cs | 210 ++++ .../Exceptions/OuterExceptionSpecs.cs | 284 +++++ 7 files changed, 1036 insertions(+), 989 deletions(-) create mode 100644 Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs diff --git a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs index 4b4c577112..21932b5467 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; @@ -148,996 +148,8 @@ public void ThrowExactly_when_subject_throws_expected_exception_it_should_not_do // Act / Assert act.Should().ThrowExactly(); } - - #region Outer Exceptions - - [Fact] - public void When_subject_throws_expected_exception_with_an_expected_message_it_should_not_do_anything() - { - // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("some message")); - - // Act / Assert - testSubject.Invoking(x => x.Do()).Should().Throw().WithMessage("some message"); - } - - [Fact] - public void When_subject_throws_expected_exception_but_with_unexpected_message_it_should_throw() - { - // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("some")); - - try - { - // Act - testSubject - .Invoking(x => x.Do()) - .Should().Throw() - .WithMessage("some message"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - // Assert - ex.Message.Should().Match( - "Expected exception message to match the equivalent of*\"some message\", but*\"some\" does not*"); - } - } - - [Fact] - public void When_subject_throws_expected_exception_with_message_starting_with_expected_message_it_should_not_throw() - { - // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("expected message")); - - // Act - Action action = testSubject.Do; - - // Assert - action.Should().Throw() - .WithMessage("expected mes*"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_subject_throws_expected_exception_with_message_that_does_not_start_with_expected_message_it_should_throw() - { - // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("OxpectOd message")); - - // Act - Action action = () => testSubject - .Invoking(s => s.Do()) - .Should().Throw() - .WithMessage("Expected mes"); - - // Assert - action.Should().Throw() - .WithMessage("Expected exception message to match the equivalent of*\"Expected mes*\", but*\"OxpectOd message\" does not*"); - } - - [Fact] - public void When_subject_throws_expected_exception_with_message_starting_with_expected_equivalent_message_it_should_not_throw() - { - // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("Expected Message")); - - // Act - Action action = testSubject.Do; - - // Assert - action.Should().Throw() - .WithMessage("expected mes*"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_subject_throws_expected_exception_with_message_that_does_not_start_with_equivalent_message_it_should_throw() - { - // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("OxpectOd message")); - - // Act - Action action = () => testSubject - .Invoking(s => s.Do()) - .Should().Throw() - .WithMessage("expected mes"); - - // Assert - action.Should().Throw() - .WithMessage("Expected exception message to match the equivalent of*\"expected mes*\", but*\"OxpectOd message\" does not*"); - } - - [Fact] - public void When_subject_throws_some_exception_with_unexpected_message_it_should_throw_with_clear_description() - { - // Arrange - Does subjectThatThrows = Does.Throw(new InvalidOperationException("message1")); - - try - { - // Act - subjectThatThrows - .Invoking(x => x.Do()) - .Should().Throw() - .WithMessage("message2", "because we want to test the failure {0}", "message"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - // Assert - ex.Message.Should().Match( - "Expected exception message to match the equivalent of \"message2\" because we want to test the failure message, but \"message1\" does not*"); - } - } - - [Fact] - public void When_subject_throws_some_exception_with_an_empty_message_it_should_throw_with_clear_description() - { - // Arrange - Does subjectThatThrows = Does.Throw(new InvalidOperationException("")); - - try - { - // Act - subjectThatThrows - .Invoking(x => x.Do()) - .Should().Throw() - .WithMessage("message2"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - // Assert - ex.Message.Should().Match( - "Expected exception message to match the equivalent of \"message2\"*, but \"\"*"); - } - } - - [Fact] - public void When_subject_throws_some_exception_with_message_which_contains_complete_expected_exception_and_more_it_should_throw() - { - // Arrange - Does subjectThatThrows = Does.Throw(new ArgumentNullException("someParam", "message2")); - - try - { - // Act - subjectThatThrows - .Invoking(x => x.Do("something")) - .Should().Throw() - .WithMessage("message2"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - // Assert - ex.Message.Should().Match( - "Expected exception message to match the equivalent of*\"message2\", but*message2*someParam*"); - } - } - - [Fact] - public void When_no_exception_was_thrown_but_one_was_expected_it_should_clearly_report_that() - { - try - { - // Arrange - Does testSubject = Does.NotThrow(); - - // Act - testSubject.Invoking(x => x.Do()).Should().Throw("because {0} should do that", "Does.Do"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - // Assert - ex.Message.Should().Be( - "Expected a to be thrown because Does.Do should do that, but no exception was thrown."); - } - } - - [Fact] - public void When_subject_throws_another_exception_than_expected_it_should_include_details_of_that_exception() - { - // Arrange - var actualException = new ArgumentException(); - - Does testSubject = Does.Throw(actualException); - - try - { - // Act - testSubject - .Invoking(x => x.Do()) - .Should().Throw("because {0} should throw that one", "Does.Do"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - // Assert - ex.Message.Should().StartWith( - "Expected a to be thrown because Does.Do should throw that one, but found :"); - - ex.Message.Should().Contain(actualException.Message); - } - } - - [Fact] - public void When_subject_throws_exception_with_message_with_braces_but_a_different_message_is_expected_it_should_report_that() - { - // Arrange - Does subjectThatThrows = Does.Throw(new Exception("message with {}")); - - try - { - // Act - subjectThatThrows - .Invoking(x => x.Do("something")) - .Should().Throw() - .WithMessage("message without"); - - throw new XunitException("this point should not be reached"); - } - catch (XunitException ex) - { - // Assert - ex.Message.Should().Match( - "Expected exception message to match the equivalent of*\"message without\"*, but*\"message with {}*"); - } - } - - [Fact] - public void When_asserting_with_an_aggregate_exception_type_the_asserts_should_occur_against_the_aggregate_exception() - { - // Arrange - Does testSubject = Does.Throw(new AggregateException("Outer Message", new Exception("Inner Message"))); - - // Act - Action act = testSubject.Do; - - // Assert - act.Should().Throw() - .WithMessage("Outer Message*") - .WithInnerException() - .WithMessage("Inner Message"); - } - - [Fact] - public void When_asserting_with_an_aggregate_exception_and_inner_exception_type_from_argument_the_asserts_should_occur_against_the_aggregate_exception() - { - // Arrange - Does testSubject = Does.Throw(new AggregateException("Outer Message", new Exception("Inner Message"))); - - // Act - Action act = testSubject.Do; - - // Assert - act.Should().Throw() - .WithMessage("Outer Message*") - .WithInnerException(typeof(Exception)) - .WithMessage("Inner Message"); - } - - #endregion - - #region Inner Exceptions - - [Fact] - public void When_subject_throws_an_exception_with_the_expected_inner_exception_it_should_not_do_anything() - { - // Arrange - Does testSubject = Does.Throw(new Exception("", new ArgumentException())); - - // Act / Assert - testSubject - .Invoking(x => x.Do()) - .Should().Throw() - .WithInnerException(); - } - - [Fact] - public void When_subject_throws_an_exception_with_the_expected_inner_base_exception_it_should_not_do_anything() - { - // Arrange - Does testSubject = Does.Throw(new Exception("", new ArgumentNullException())); - - // Act / Assert - testSubject - .Invoking(x => x.Do()) - .Should().Throw() - .WithInnerException(); - } - - [Fact] - public void When_subject_throws_an_exception_with_the_expected_inner_exception_from_argument_it_should_not_do_anything() - { - // Arrange - Does testSubject = Does.Throw(new Exception("", new ArgumentException())); - - // Act / Assert - testSubject - .Invoking(x => x.Do()) - .Should().Throw() - .WithInnerException(typeof(ArgumentException)); - } - - [Fact] - public void WithInnerExceptionExactly_no_parameters_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() - { - // Arrange - var innerException = new ArgumentNullException(); - - Action act = () => throw new BadImageFormatException("", innerException); - - try - { - // Act - act.Should().Throw() - .WithInnerExceptionExactly(); - - throw new XunitException("This point should not be reached."); - } - catch (XunitException ex) - { - // Assert - var expectedMessage = BuildExpectedMessageForWithInnerExceptionExactly("Expected inner System.ArgumentException, but found System.ArgumentNullException with message", innerException.Message); - - ex.Message.Should().Be(expectedMessage); - } - } - - [Fact] - public void WithInnerExceptionExactly_no_parameters_when_subject_throws_expected_inner_exception_it_should_not_do_anything() - { - // Arrange - Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); - - // Act / Assert - act.Should().Throw() - .WithInnerExceptionExactly(); - } - - [Fact] - public void WithInnerExceptionExactly_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() - { - // Arrange - var innerException = new ArgumentNullException(); - - Action act = () => throw new BadImageFormatException("", innerException); - - try - { - // Act - act.Should().Throw() - .WithInnerExceptionExactly("because {0} should do just that", "the action"); - - throw new XunitException("This point should not be reached."); - } - catch (XunitException ex) - { - // Assert - var expectedMessage = BuildExpectedMessageForWithInnerExceptionExactly("Expected inner System.ArgumentException because the action should do just that, but found System.ArgumentNullException with message", innerException.Message); - - ex.Message.Should().Be(expectedMessage); - } - } - - [Fact] - public void WithInnerExceptionExactly_with_type_exception_when_subject_throws_expected_inner_exception_it_should_not_do_anything() - { - // Arrange - Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); - - // Act / Assert - act.Should().Throw() - .WithInnerExceptionExactly(typeof(ArgumentNullException), "because {0} should do just that", "the action"); - } - - [Fact] - public void WithInnerExceptionExactly_with_type_exception_no_parameters_when_subject_throws_expected_inner_exception_it_should_not_do_anything() - { - // Arrange - Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); - - // Act / Assert - act.Should().Throw() - .WithInnerExceptionExactly(typeof(ArgumentNullException)); - } - - [Fact] - public void WithInnerExceptionExactly_with_type_exception_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() - { - // Arrange - var innerException = new ArgumentNullException(); - - Action act = () => throw new BadImageFormatException("", innerException); - - try - { - // Act - act.Should().Throw() - .WithInnerExceptionExactly(typeof(ArgumentException), "because {0} should do just that", "the action"); - - throw new XunitException("This point should not be reached."); - } - catch (XunitException ex) - { - // Assert - var expectedMessage = BuildExpectedMessageForWithInnerExceptionExactly("Expected inner System.ArgumentException because the action should do just that, but found System.ArgumentNullException with message", innerException.Message); - - ex.Message.Should().Be(expectedMessage); - } - } - - [Fact] - public void WithInnerExceptionExactly_when_subject_throws_expected_inner_exception_it_should_not_do_anything() - { - // Arrange - Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); - - // Act / Assert - act.Should().Throw() - .WithInnerExceptionExactly("because {0} should do just that", "the action"); - } - - private static string BuildExpectedMessageForWithInnerExceptionExactly(string because, string innerExceptionMessage) - { - var expectedMessage = $"{because} \"{innerExceptionMessage}\"."; - - return expectedMessage; - } - - [Fact] - public void When_subject_throws_an_exception_with_an_unexpected_inner_exception_it_should_throw_with_clear_description() - { - // Arrange - var innerException = new NullReferenceException(); - - Does testSubject = Does.Throw(new Exception("", innerException)); - - try - { - // Act - testSubject - .Invoking(x => x.Do()) - .Should().Throw() - .WithInnerException("because {0} should do just that", "Does.Do"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException exc) - { - // Assert - exc.Message.Should().StartWith( - "Expected inner System.ArgumentException because Does.Do should do just that, but found System.NullReferenceException"); - - exc.Message.Should().Contain(innerException.Message); - } - } - - [Fact] - public void When_subject_throws_an_exception_without_expected_inner_exception_it_should_throw_with_clear_description() - { - try - { - Does testSubject = Does.Throw(); - - testSubject.Invoking(x => x.Do()).Should().Throw() - .WithInnerException(); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - ex.Message.Should().Be( - "Expected inner System.InvalidOperationException, but the thrown exception has no inner exception."); - } - } - - [Fact] - public void When_subject_throws_an_exception_without_expected_inner_exception_and_has_reason_it_should_throw_with_clear_description() - { - try - { - Does testSubject = Does.Throw(); - - testSubject.Invoking(x => x.Do()).Should().Throw() - .WithInnerException("because {0} should do that", "Does.Do"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException ex) - { - ex.Message.Should().Be( - "Expected inner System.InvalidOperationException because Does.Do should do that, but the thrown exception has no inner exception."); - } - } - - [Fact] - public void When_an_inner_exception_matches_exactly_it_should_allow_chaining_more_asserts_on_that_exception_type() - { - // Act - Action act = () => - throw new ArgumentException("OuterMessage", new InvalidOperationException("InnerMessage")); - - // Assert - act - .Should().ThrowExactly() - .WithInnerExceptionExactly() - .Where(i => i.Message == "InnerMessage"); - } - - [Fact] - public void When_an_inner_exception_matches_exactly_it_should_allow_chaining_more_asserts_on_that_exception_type_from_argument() - { - // Act - Action act = () => - throw new ArgumentException("OuterMessage", new InvalidOperationException("InnerMessage")); - - // Assert - act - .Should().ThrowExactly() - .WithInnerExceptionExactly(typeof(InvalidOperationException)) - .Where(i => i.Message == "InnerMessage"); - } - - [Fact] - public void When_injecting_a_null_predicate_it_should_throw() - { - // Arrange - Action act = () => throw new Exception(); - - // Act - Action act2 = () => act.Should().Throw() - .Where(exceptionExpression: null); - - // Act - act2.Should().ThrowExactly() - .WithParameterName("exceptionExpression"); - } - - #endregion - - #region Miscellaneous - - [Fact] - public void When_getting_value_of_property_of_thrown_exception_it_should_return_value_of_property() - { - // Arrange - const string SomeParamNameValue = "param"; - Does target = Does.Throw(new ExceptionWithProperties(SomeParamNameValue)); - - // Act - Action act = target.Do; - - // Assert - act.Should().Throw().And.Property.Should().Be(SomeParamNameValue); - } - - [Fact] - public void When_validating_a_subject_against_multiple_conditions_it_should_support_chaining() - { - // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("message", new ArgumentException("inner message"))); - - // Act / Assert - testSubject - .Invoking(x => x.Do()) - .Should().Throw() - .WithInnerException() - .WithMessage("inner message"); - } - - [Fact] - public void When_a_yielding_enumerable_throws_an_expected_exception_it_should_not_throw() - { - // Act - Func> act = () => MethodThatUsesYield("aaa!aaa"); - - // Assert - act.Enumerating().Should().Throw(); - } - - private static IEnumerable MethodThatUsesYield(string bar) - { - foreach (var character in bar) - { - if (character.Equals('!')) - { - throw new Exception("No exclamation marks allowed."); - } - - yield return char.ToUpperInvariant(character); - } - } - - [Fact] - public void When_custom_condition_is_not_met_it_should_throw() - { - // Arrange - Action act = () => throw new ArgumentException(""); - - try - { - // Act - act - .Should().Throw("") - .Where(e => e.Message.Length > 0, "an exception must have a message"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException exc) - { - // Assert - exc.Message.Should().StartWith( - "Expected exception where (e.Message.Length > 0) because an exception must have a message, but the condition was not met"); - } - } - - [Fact] - public void When_a_2nd_condition_is_not_met_it_should_throw() - { - // Arrange - Action act = () => throw new ArgumentException("Fail"); - - try - { - // Act - act - .Should().Throw("") - .Where(e => e.Message.Length > 0) - .Where(e => e.Message == "Error"); - - throw new XunitException("This point should not be reached"); - } - catch (XunitException exc) - { - // Assert - exc.Message.Should().StartWith( - "Expected exception where (e.Message == \"Error\"), but the condition was not met"); - } - catch (Exception exc) - { - exc.Message.Should().StartWith( - "Expected exception where (e.Message == \"Error\"), but the condition was not met"); - } - } - - [Fact] - public void When_custom_condition_is_met_it_should_not_throw() - { - // Arrange / Act - Action act = () => throw new ArgumentException(""); - - // Assert - act - .Should().Throw() - .Where(e => e.Message.Length == 0); - } - - [Fact] - public void When_two_exceptions_are_thrown_and_the_assertion_assumes_there_can_only_be_one_it_should_fail() - { - // Arrange - Does testSubject = Does.Throw(new AggregateException(new Exception(), new Exception())); - Action throwingMethod = testSubject.Do; - - // Act - Action action = () => throwingMethod.Should().Throw().And.Message.Should(); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_an_exception_of_a_different_type_is_thrown_it_should_include_the_type_of_the_thrown_exception() - { - // Arrange - Action throwException = () => throw new ExceptionWithEmptyToString(); - - // Act - Action act = - () => throwException.Should().Throw(); - - // Assert - act.Should().Throw() - .WithMessage($"*System.ArgumentNullException*{typeof(ExceptionWithEmptyToString)}*"); - } - - [Fact] - public void When_a_method_throws_with_a_matching_parameter_name_it_should_succeed() - { - // Arrange - Action throwException = () => throw new ArgumentNullException("someParameter"); - - // Act - Action act = () => - throwException.Should().Throw() - .WithParameterName("someParameter"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_method_throws_with_a_non_matching_parameter_name_it_should_fail_with_a_descriptive_message() - { - // Arrange - Action throwException = () => throw new ArgumentNullException("someOtherParameter"); - - // Act - Action act = () => - throwException.Should().Throw() - .WithParameterName("someParameter", "we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("*with parameter name \"someParameter\"*we want to test the failure message*\"someOtherParameter\"*"); - } - - [Fact] - public void When_invoking_a_function_on_a_null_subject_it_should_throw() - { - // Arrange - Does someClass = null; - - // Act - Action act = () => someClass.Invoking(d => d.ToString()); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("subject"); - } - - [Fact] - public void When_invoking_a_function_with_null_it_should_throw() - { - // Arrange - object someClass = new(); - - // Act - Action act = () => someClass.Invoking((Func)null); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("action"); - } - - [Fact] - public void When_invoking_an_action_on_a_null_subject_it_should_throw() - { - // Arrange - Does someClass = null; - - // Act - Action act = () => someClass.Invoking(d => d.Do()); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("subject"); - } - - [Fact] - public void When_invoking_an_action_with_null_it_should_throw() - { - // Arrange - Does someClass = Does.NotThrow(); - - // Act - Action act = () => someClass.Invoking(null).Should().NotThrow(); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("action"); - } - - #endregion - - #region Not Throw - [Fact] - public void When_subject_is_null_when_an_exception_should_not_be_thrown_it_should_throw() - { - // Arrange - Action act = null; - - // Act - Action action = () => act.Should().NotThrow("because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage("*because we want to test the failure message*found *"); - } - - [Fact] - public void When_a_specific_exception_should_not_be_thrown_but_it_was_it_should_throw() - { - // Arrange - Does foo = Does.Throw(new ArgumentException("An exception was forced")); - - // Act - Action action = - () => foo.Invoking(f => f.Do()).Should().NotThrow("we passed valid arguments"); - - // Assert - action - .Should().Throw().WithMessage( - "Did not expect System.ArgumentException because we passed valid arguments, " + - "but found*with message \"An exception was forced\"*"); - } - - [Fact] - public void When_a_specific_exception_should_not_be_thrown_but_another_was_it_should_succeed() - { - // Arrange - Does foo = Does.Throw(); - - // Act / Assert - foo.Invoking(f => f.Do()).Should().NotThrow(); - } - - [Fact] - public void When_no_exception_should_be_thrown_by_sync_over_async_it_should_not_throw() - { - // Arrange - Action act = () => Task.Delay(0).Wait(0); - - // Act / Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_no_exception_should_be_thrown_but_it_was_it_should_throw() - { - // Arrange - Does foo = Does.Throw(new ArgumentException("An exception was forced")); - - // Act - Action action = () => foo.Invoking(f => f.Do()).Should().NotThrow("we passed valid arguments"); - - // Assert - action - .Should().Throw().WithMessage( - "Did not expect any exception because we passed valid arguments, " + - "but found System.ArgumentException with message \"An exception was forced\"*"); - } - - [Fact] - public void When_no_exception_should_be_thrown_and_none_was_it_should_not_throw() - { - // Arrange - Does foo = Does.NotThrow(); - - // Act / Assert - foo.Invoking(f => f.Do()).Should().NotThrow(); - } - - [Fact] - public void When_subject_is_null_when_it_should_not_throw_it_should_throw() - { - // Arrange - Action act = null; - - // Act - Action action = () => act.Should().NotThrowAfter(0.Milliseconds(), 0.Milliseconds(), - "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage("*because we want to test the failure message*found *"); - } - -#pragma warning disable CS1998 - [Fact] - public void When_subject_is_async_it_should_throw() - { - // Arrange - Action someAsyncAction = async () => { }; - - // Act - Action action = () => - someAsyncAction.Should().NotThrowAfter(1.Milliseconds(), 1.Milliseconds()); - - // Assert - action.Should().Throw() - .WithMessage("Cannot use action assertions on an async void method.*"); - } -#pragma warning restore CS1998 - - [Fact] - public void When_wait_time_is_negative_it_should_throw() - { - // Arrange - var waitTime = -1.Milliseconds(); - var pollInterval = 10.Milliseconds(); - Action someAction = () => { }; - - // Act - Action action = () => - someAction.Should().NotThrowAfter(waitTime, pollInterval); - - // Assert - action.Should().Throw() - .WithMessage("* value of waitTime must be non-negative*"); - } - - [Fact] - public void When_poll_interval_is_negative_it_should_throw() - { - // Arrange - var waitTime = 10.Milliseconds(); - var pollInterval = -1.Milliseconds(); - Action someAction = () => { }; - - // Act - Action action = () => - someAction.Should().NotThrowAfter(waitTime, pollInterval); - - // Assert - action.Should().Throw() - .WithMessage("* value of pollInterval must be non-negative*"); - } - - [Fact] - public void When_no_exception_should_be_thrown_after_wait_time_but_it_was_it_should_throw() - { - // Arrange - var waitTime = 100.Milliseconds(); - var pollInterval = 10.Milliseconds(); - - var clock = new FakeClock(); - var timer = clock.StartTimer(); - - Action throwLongerThanWaitTime = () => - { - if (timer.Elapsed < waitTime.Multiply(1.5)) - { - throw new ArgumentException("An exception was forced"); - } - }; - - // Act - Action action = () => - throwLongerThanWaitTime.Should(clock).NotThrowAfter(waitTime, pollInterval, "we passed valid arguments"); - - // Assert - action.Should().Throw() - .WithMessage("Did not expect any exceptions after 100ms because we passed valid arguments*"); - } - - [Fact] - public void When_no_exception_should_be_thrown_after_wait_time_and_none_was_it_should_not_throw() - { - // Arrange - var clock = new FakeClock(); - var timer = clock.StartTimer(); - var waitTime = 100.Milliseconds(); - var pollInterval = 10.Milliseconds(); - - Action throwShorterThanWaitTime = () => - { - if (timer.Elapsed <= waitTime.Divide(2)) - { - throw new ArgumentException("An exception was forced"); - } - }; - - // Act - Action act = () => throwShorterThanWaitTime.Should(clock).NotThrowAfter(waitTime, pollInterval); - - // Assert - act.Should().NotThrow(); - } } - #endregion - public class SomeTestClass { internal const string ExceptionMessage = "someMessage"; diff --git a/Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs new file mode 100644 index 0000000000..de80d7783c --- /dev/null +++ b/Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs @@ -0,0 +1,284 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Exceptions +{ + public class InnerExceptionSpecs + { + [Fact] + public void When_subject_throws_an_exception_with_the_expected_inner_exception_it_should_not_do_anything() + { + // Arrange + Does testSubject = Does.Throw(new Exception("", new ArgumentException())); + + // Act / Assert + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithInnerException(); + } + + [Fact] + public void When_subject_throws_an_exception_with_the_expected_inner_base_exception_it_should_not_do_anything() + { + // Arrange + Does testSubject = Does.Throw(new Exception("", new ArgumentNullException())); + + // Act / Assert + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithInnerException(); + } + + [Fact] + public void When_subject_throws_an_exception_with_the_expected_inner_exception_from_argument_it_should_not_do_anything() + { + // Arrange + Does testSubject = Does.Throw(new Exception("", new ArgumentException())); + + // Act / Assert + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithInnerException(typeof(ArgumentException)); + } + + [Fact] + public void WithInnerExceptionExactly_no_parameters_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() + { + // Arrange + var innerException = new ArgumentNullException(); + + Action act = () => throw new BadImageFormatException("", innerException); + + try + { + // Act + act.Should().Throw() + .WithInnerExceptionExactly(); + + throw new XunitException("This point should not be reached."); + } + catch (XunitException ex) + { + // Assert + var expectedMessage = BuildExpectedMessageForWithInnerExceptionExactly("Expected inner System.ArgumentException, but found System.ArgumentNullException with message", innerException.Message); + + ex.Message.Should().Be(expectedMessage); + } + } + + [Fact] + public void WithInnerExceptionExactly_no_parameters_when_subject_throws_expected_inner_exception_it_should_not_do_anything() + { + // Arrange + Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); + + // Act / Assert + act.Should().Throw() + .WithInnerExceptionExactly(); + } + + [Fact] + public void WithInnerExceptionExactly_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() + { + // Arrange + var innerException = new ArgumentNullException(); + + Action act = () => throw new BadImageFormatException("", innerException); + + try + { + // Act + act.Should().Throw() + .WithInnerExceptionExactly("because {0} should do just that", "the action"); + + throw new XunitException("This point should not be reached."); + } + catch (XunitException ex) + { + // Assert + var expectedMessage = BuildExpectedMessageForWithInnerExceptionExactly("Expected inner System.ArgumentException because the action should do just that, but found System.ArgumentNullException with message", innerException.Message); + + ex.Message.Should().Be(expectedMessage); + } + } + + [Fact] + public void WithInnerExceptionExactly_with_type_exception_when_subject_throws_expected_inner_exception_it_should_not_do_anything() + { + // Arrange + Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); + + // Act / Assert + act.Should().Throw() + .WithInnerExceptionExactly(typeof(ArgumentNullException), "because {0} should do just that", "the action"); + } + + [Fact] + public void WithInnerExceptionExactly_with_type_exception_no_parameters_when_subject_throws_expected_inner_exception_it_should_not_do_anything() + { + // Arrange + Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); + + // Act / Assert + act.Should().Throw() + .WithInnerExceptionExactly(typeof(ArgumentNullException)); + } + + [Fact] + public void WithInnerExceptionExactly_with_type_exception_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() + { + // Arrange + var innerException = new ArgumentNullException(); + + Action act = () => throw new BadImageFormatException("", innerException); + + try + { + // Act + act.Should().Throw() + .WithInnerExceptionExactly(typeof(ArgumentException), "because {0} should do just that", "the action"); + + throw new XunitException("This point should not be reached."); + } + catch (XunitException ex) + { + // Assert + var expectedMessage = BuildExpectedMessageForWithInnerExceptionExactly("Expected inner System.ArgumentException because the action should do just that, but found System.ArgumentNullException with message", innerException.Message); + + ex.Message.Should().Be(expectedMessage); + } + } + + [Fact] + public void WithInnerExceptionExactly_when_subject_throws_expected_inner_exception_it_should_not_do_anything() + { + // Arrange + Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); + + // Act / Assert + act.Should().Throw() + .WithInnerExceptionExactly("because {0} should do just that", "the action"); + } + + private static string BuildExpectedMessageForWithInnerExceptionExactly(string because, string innerExceptionMessage) + { + var expectedMessage = $"{because} \"{innerExceptionMessage}\"."; + + return expectedMessage; + } + + [Fact] + public void When_subject_throws_an_exception_with_an_unexpected_inner_exception_it_should_throw_with_clear_description() + { + // Arrange + var innerException = new NullReferenceException(); + + Does testSubject = Does.Throw(new Exception("", innerException)); + + try + { + // Act + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithInnerException("because {0} should do just that", "Does.Do"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException exc) + { + // Assert + exc.Message.Should().StartWith( + "Expected inner System.ArgumentException because Does.Do should do just that, but found System.NullReferenceException"); + + exc.Message.Should().Contain(innerException.Message); + } + } + + [Fact] + public void When_subject_throws_an_exception_without_expected_inner_exception_it_should_throw_with_clear_description() + { + try + { + Does testSubject = Does.Throw(); + + testSubject.Invoking(x => x.Do()).Should().Throw() + .WithInnerException(); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + ex.Message.Should().Be( + "Expected inner System.InvalidOperationException, but the thrown exception has no inner exception."); + } + } + + [Fact] + public void When_subject_throws_an_exception_without_expected_inner_exception_and_has_reason_it_should_throw_with_clear_description() + { + try + { + Does testSubject = Does.Throw(); + + testSubject.Invoking(x => x.Do()).Should().Throw() + .WithInnerException("because {0} should do that", "Does.Do"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + ex.Message.Should().Be( + "Expected inner System.InvalidOperationException because Does.Do should do that, but the thrown exception has no inner exception."); + } + } + + [Fact] + public void When_an_inner_exception_matches_exactly_it_should_allow_chaining_more_asserts_on_that_exception_type() + { + // Act + Action act = () => + throw new ArgumentException("OuterMessage", new InvalidOperationException("InnerMessage")); + + // Assert + act + .Should().ThrowExactly() + .WithInnerExceptionExactly() + .Where(i => i.Message == "InnerMessage"); + } + + [Fact] + public void When_an_inner_exception_matches_exactly_it_should_allow_chaining_more_asserts_on_that_exception_type_from_argument() + { + // Act + Action act = () => + throw new ArgumentException("OuterMessage", new InvalidOperationException("InnerMessage")); + + // Assert + act + .Should().ThrowExactly() + .WithInnerExceptionExactly(typeof(InvalidOperationException)) + .Where(i => i.Message == "InnerMessage"); + } + + [Fact] + public void When_injecting_a_null_predicate_it_should_throw() + { + // Arrange + Action act = () => throw new Exception(); + + // Act + Action act2 = () => act.Should().Throw() + .Where(exceptionExpression: null); + + // Act + act2.Should().ThrowExactly() + .WithParameterName("exceptionExpression"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs new file mode 100644 index 0000000000..256205fd28 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs @@ -0,0 +1,36 @@ +using System; +using Xunit; + +namespace FluentAssertions.Specs.Exceptions +{ + public class InvokingActionSpecs + { + [Fact] + public void When_invoking_an_action_on_a_null_subject_it_should_throw() + { + // Arrange + Does someClass = null; + + // Act + Action act = () => someClass.Invoking(d => d.Do()); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("subject"); + } + + [Fact] + public void When_invoking_an_action_with_null_it_should_throw() + { + // Arrange + Does someClass = Does.NotThrow(); + + // Act + Action act = () => someClass.Invoking(null).Should().NotThrow(); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("action"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs new file mode 100644 index 0000000000..1a97c41883 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs @@ -0,0 +1,36 @@ +using System; +using Xunit; + +namespace FluentAssertions.Specs.Exceptions +{ + public class InvokingFunctionSpecs + { + [Fact] + public void When_invoking_a_function_on_a_null_subject_it_should_throw() + { + // Arrange + Does someClass = null; + + // Act + Action act = () => someClass.Invoking(d => d.ToString()); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("subject"); + } + + [Fact] + public void When_invoking_a_function_with_null_it_should_throw() + { + // Arrange + object someClass = new(); + + // Act + Action act = () => someClass.Invoking((Func)null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("action"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs new file mode 100644 index 0000000000..9132f73b7f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Exceptions +{ + public class MiscellaneousExceptionSpecs + { + [Fact] + public void When_getting_value_of_property_of_thrown_exception_it_should_return_value_of_property() + { + // Arrange + const string SomeParamNameValue = "param"; + Does target = Does.Throw(new ExceptionWithProperties(SomeParamNameValue)); + + // Act + Action act = target.Do; + + // Assert + act.Should().Throw().And.Property.Should().Be(SomeParamNameValue); + } + + [Fact] + public void When_validating_a_subject_against_multiple_conditions_it_should_support_chaining() + { + // Arrange + Does testSubject = Does.Throw(new InvalidOperationException("message", new ArgumentException("inner message"))); + + // Act / Assert + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithInnerException() + .WithMessage("inner message"); + } + + [Fact] + public void When_a_yielding_enumerable_throws_an_expected_exception_it_should_not_throw() + { + // Act + Func> act = () => MethodThatUsesYield("aaa!aaa"); + + // Assert + act.Enumerating().Should().Throw(); + } + + private static IEnumerable MethodThatUsesYield(string bar) + { + foreach (var character in bar) + { + if (character.Equals('!')) + { + throw new Exception("No exclamation marks allowed."); + } + + yield return char.ToUpperInvariant(character); + } + } + + [Fact] + public void When_custom_condition_is_not_met_it_should_throw() + { + // Arrange + Action act = () => throw new ArgumentException(""); + + try + { + // Act + act + .Should().Throw("") + .Where(e => e.Message.Length > 0, "an exception must have a message"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException exc) + { + // Assert + exc.Message.Should().StartWith( + "Expected exception where (e.Message.Length > 0) because an exception must have a message, but the condition was not met"); + } + } + + [Fact] + public void When_a_2nd_condition_is_not_met_it_should_throw() + { + // Arrange + Action act = () => throw new ArgumentException("Fail"); + + try + { + // Act + act + .Should().Throw("") + .Where(e => e.Message.Length > 0) + .Where(e => e.Message == "Error"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException exc) + { + // Assert + exc.Message.Should().StartWith( + "Expected exception where (e.Message == \"Error\"), but the condition was not met"); + } + catch (Exception exc) + { + exc.Message.Should().StartWith( + "Expected exception where (e.Message == \"Error\"), but the condition was not met"); + } + } + + [Fact] + public void When_custom_condition_is_met_it_should_not_throw() + { + // Arrange / Act + Action act = () => throw new ArgumentException(""); + + // Assert + act + .Should().Throw() + .Where(e => e.Message.Length == 0); + } + + [Fact] + public void When_two_exceptions_are_thrown_and_the_assertion_assumes_there_can_only_be_one_it_should_fail() + { + // Arrange + Does testSubject = Does.Throw(new AggregateException(new Exception(), new Exception())); + Action throwingMethod = testSubject.Do; + + // Act + Action action = () => throwingMethod.Should().Throw().And.Message.Should(); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_an_exception_of_a_different_type_is_thrown_it_should_include_the_type_of_the_thrown_exception() + { + // Arrange + Action throwException = () => throw new ExceptionWithEmptyToString(); + + // Act + Action act = + () => throwException.Should().Throw(); + + // Assert + act.Should().Throw() + .WithMessage($"*System.ArgumentNullException*{typeof(ExceptionWithEmptyToString)}*"); + } + + [Fact] + public void When_a_method_throws_with_a_matching_parameter_name_it_should_succeed() + { + // Arrange + Action throwException = () => throw new ArgumentNullException("someParameter"); + + // Act + Action act = () => + throwException.Should().Throw() + .WithParameterName("someParameter"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_method_throws_with_a_non_matching_parameter_name_it_should_fail_with_a_descriptive_message() + { + // Arrange + Action throwException = () => throw new ArgumentNullException("someOtherParameter"); + + // Act + Action act = () => + throwException.Should().Throw() + .WithParameterName("someParameter", "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("*with parameter name \"someParameter\"*we want to test the failure message*\"someOtherParameter\"*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs new file mode 100644 index 0000000000..67a146e5b4 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs @@ -0,0 +1,210 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +#if NETFRAMEWORK +using FluentAssertions.Specs.Common; +#endif +using Xunit; +using Xunit.Sdk; + +using static FluentAssertions.Extensions.FluentTimeSpanExtensions; + +namespace FluentAssertions.Specs.Exceptions +{ + public class NotThrowSpecs + { + [Fact] + public void When_subject_is_null_when_an_exception_should_not_be_thrown_it_should_throw() + { + // Arrange + Action act = null; + + // Act + Action action = () => act.Should().NotThrow("because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage("*because we want to test the failure message*found *"); + } + + [Fact] + public void When_a_specific_exception_should_not_be_thrown_but_it_was_it_should_throw() + { + // Arrange + Does foo = Does.Throw(new ArgumentException("An exception was forced")); + + // Act + Action action = + () => foo.Invoking(f => f.Do()).Should().NotThrow("we passed valid arguments"); + + // Assert + action + .Should().Throw().WithMessage( + "Did not expect System.ArgumentException because we passed valid arguments, " + + "but found*with message \"An exception was forced\"*"); + } + + [Fact] + public void When_a_specific_exception_should_not_be_thrown_but_another_was_it_should_succeed() + { + // Arrange + Does foo = Does.Throw(); + + // Act / Assert + foo.Invoking(f => f.Do()).Should().NotThrow(); + } + + [Fact] + public void When_no_exception_should_be_thrown_by_sync_over_async_it_should_not_throw() + { + // Arrange + Action act = () => Task.Delay(0).Wait(0); + + // Act / Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_no_exception_should_be_thrown_but_it_was_it_should_throw() + { + // Arrange + Does foo = Does.Throw(new ArgumentException("An exception was forced")); + + // Act + Action action = () => foo.Invoking(f => f.Do()).Should().NotThrow("we passed valid arguments"); + + // Assert + action + .Should().Throw().WithMessage( + "Did not expect any exception because we passed valid arguments, " + + "but found System.ArgumentException with message \"An exception was forced\"*"); + } + + [Fact] + public void When_no_exception_should_be_thrown_and_none_was_it_should_not_throw() + { + // Arrange + Does foo = Does.NotThrow(); + + // Act / Assert + foo.Invoking(f => f.Do()).Should().NotThrow(); + } + + [Fact] + public void When_subject_is_null_when_it_should_not_throw_it_should_throw() + { + // Arrange + Action act = null; + + // Act + Action action = () => act.Should().NotThrowAfter(0.Milliseconds(), 0.Milliseconds(), + "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage("*because we want to test the failure message*found *"); + } + +#pragma warning disable CS1998 + [Fact] + public void When_subject_is_async_it_should_throw() + { + // Arrange + Action someAsyncAction = async () => { }; + + // Act + Action action = () => + someAsyncAction.Should().NotThrowAfter(1.Milliseconds(), 1.Milliseconds()); + + // Assert + action.Should().Throw() + .WithMessage("Cannot use action assertions on an async void method.*"); + } +#pragma warning restore CS1998 + + [Fact] + public void When_wait_time_is_negative_it_should_throw() + { + // Arrange + var waitTime = -1.Milliseconds(); + var pollInterval = 10.Milliseconds(); + Action someAction = () => { }; + + // Act + Action action = () => + someAction.Should().NotThrowAfter(waitTime, pollInterval); + + // Assert + action.Should().Throw() + .WithMessage("* value of waitTime must be non-negative*"); + } + + [Fact] + public void When_poll_interval_is_negative_it_should_throw() + { + // Arrange + var waitTime = 10.Milliseconds(); + var pollInterval = -1.Milliseconds(); + Action someAction = () => { }; + + // Act + Action action = () => + someAction.Should().NotThrowAfter(waitTime, pollInterval); + + // Assert + action.Should().Throw() + .WithMessage("* value of pollInterval must be non-negative*"); + } + + [Fact] + public void When_no_exception_should_be_thrown_after_wait_time_but_it_was_it_should_throw() + { + // Arrange + var waitTime = 100.Milliseconds(); + var pollInterval = 10.Milliseconds(); + + var clock = new FakeClock(); + var timer = clock.StartTimer(); + + Action throwLongerThanWaitTime = () => + { + if (timer.Elapsed < waitTime.Multiply(1.5)) + { + throw new ArgumentException("An exception was forced"); + } + }; + + // Act + Action action = () => + throwLongerThanWaitTime.Should(clock).NotThrowAfter(waitTime, pollInterval, "we passed valid arguments"); + + // Assert + action.Should().Throw() + .WithMessage("Did not expect any exceptions after 100ms because we passed valid arguments*"); + } + + [Fact] + public void When_no_exception_should_be_thrown_after_wait_time_and_none_was_it_should_not_throw() + { + // Arrange + var clock = new FakeClock(); + var timer = clock.StartTimer(); + var waitTime = 100.Milliseconds(); + var pollInterval = 10.Milliseconds(); + + Action throwShorterThanWaitTime = () => + { + if (timer.Elapsed <= waitTime.Divide(2)) + { + throw new ArgumentException("An exception was forced"); + } + }; + + // Act + Action act = () => throwShorterThanWaitTime.Should(clock).NotThrowAfter(waitTime, pollInterval); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs new file mode 100644 index 0000000000..ad4c49b1d3 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs @@ -0,0 +1,284 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Exceptions +{ + public class OuterExceptionSpecs + { + [Fact] + public void When_subject_throws_expected_exception_with_an_expected_message_it_should_not_do_anything() + { + // Arrange + Does testSubject = Does.Throw(new InvalidOperationException("some message")); + + // Act / Assert + testSubject.Invoking(x => x.Do()).Should().Throw().WithMessage("some message"); + } + + [Fact] + public void When_subject_throws_expected_exception_but_with_unexpected_message_it_should_throw() + { + // Arrange + Does testSubject = Does.Throw(new InvalidOperationException("some")); + + try + { + // Act + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithMessage("some message"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + "Expected exception message to match the equivalent of*\"some message\", but*\"some\" does not*"); + } + } + + [Fact] + public void When_subject_throws_expected_exception_with_message_starting_with_expected_message_it_should_not_throw() + { + // Arrange + Does testSubject = Does.Throw(new InvalidOperationException("expected message")); + + // Act + Action action = testSubject.Do; + + // Assert + action.Should().Throw() + .WithMessage("expected mes*"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_subject_throws_expected_exception_with_message_that_does_not_start_with_expected_message_it_should_throw() + { + // Arrange + Does testSubject = Does.Throw(new InvalidOperationException("OxpectOd message")); + + // Act + Action action = () => testSubject + .Invoking(s => s.Do()) + .Should().Throw() + .WithMessage("Expected mes"); + + // Assert + action.Should().Throw() + .WithMessage("Expected exception message to match the equivalent of*\"Expected mes*\", but*\"OxpectOd message\" does not*"); + } + + [Fact] + public void When_subject_throws_expected_exception_with_message_starting_with_expected_equivalent_message_it_should_not_throw() + { + // Arrange + Does testSubject = Does.Throw(new InvalidOperationException("Expected Message")); + + // Act + Action action = testSubject.Do; + + // Assert + action.Should().Throw() + .WithMessage("expected mes*"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_subject_throws_expected_exception_with_message_that_does_not_start_with_equivalent_message_it_should_throw() + { + // Arrange + Does testSubject = Does.Throw(new InvalidOperationException("OxpectOd message")); + + // Act + Action action = () => testSubject + .Invoking(s => s.Do()) + .Should().Throw() + .WithMessage("expected mes"); + + // Assert + action.Should().Throw() + .WithMessage("Expected exception message to match the equivalent of*\"expected mes*\", but*\"OxpectOd message\" does not*"); + } + + [Fact] + public void When_subject_throws_some_exception_with_unexpected_message_it_should_throw_with_clear_description() + { + // Arrange + Does subjectThatThrows = Does.Throw(new InvalidOperationException("message1")); + + try + { + // Act + subjectThatThrows + .Invoking(x => x.Do()) + .Should().Throw() + .WithMessage("message2", "because we want to test the failure {0}", "message"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + "Expected exception message to match the equivalent of \"message2\" because we want to test the failure message, but \"message1\" does not*"); + } + } + + [Fact] + public void When_subject_throws_some_exception_with_an_empty_message_it_should_throw_with_clear_description() + { + // Arrange + Does subjectThatThrows = Does.Throw(new InvalidOperationException("")); + + try + { + // Act + subjectThatThrows + .Invoking(x => x.Do()) + .Should().Throw() + .WithMessage("message2"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + "Expected exception message to match the equivalent of \"message2\"*, but \"\"*"); + } + } + + [Fact] + public void When_subject_throws_some_exception_with_message_which_contains_complete_expected_exception_and_more_it_should_throw() + { + // Arrange + Does subjectThatThrows = Does.Throw(new ArgumentNullException("someParam", "message2")); + + try + { + // Act + subjectThatThrows + .Invoking(x => x.Do("something")) + .Should().Throw() + .WithMessage("message2"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + "Expected exception message to match the equivalent of*\"message2\", but*message2*someParam*"); + } + } + + [Fact] + public void When_no_exception_was_thrown_but_one_was_expected_it_should_clearly_report_that() + { + try + { + // Arrange + Does testSubject = Does.NotThrow(); + + // Act + testSubject.Invoking(x => x.Do()).Should().Throw("because {0} should do that", "Does.Do"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Be( + "Expected a to be thrown because Does.Do should do that, but no exception was thrown."); + } + } + + [Fact] + public void When_subject_throws_another_exception_than_expected_it_should_include_details_of_that_exception() + { + // Arrange + var actualException = new ArgumentException(); + + Does testSubject = Does.Throw(actualException); + + try + { + // Act + testSubject + .Invoking(x => x.Do()) + .Should().Throw("because {0} should throw that one", "Does.Do"); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().StartWith( + "Expected a to be thrown because Does.Do should throw that one, but found :"); + + ex.Message.Should().Contain(actualException.Message); + } + } + + [Fact] + public void When_subject_throws_exception_with_message_with_braces_but_a_different_message_is_expected_it_should_report_that() + { + // Arrange + Does subjectThatThrows = Does.Throw(new Exception("message with {}")); + + try + { + // Act + subjectThatThrows + .Invoking(x => x.Do("something")) + .Should().Throw() + .WithMessage("message without"); + + throw new XunitException("this point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + "Expected exception message to match the equivalent of*\"message without\"*, but*\"message with {}*"); + } + } + + [Fact] + public void When_asserting_with_an_aggregate_exception_type_the_asserts_should_occur_against_the_aggregate_exception() + { + // Arrange + Does testSubject = Does.Throw(new AggregateException("Outer Message", new Exception("Inner Message"))); + + // Act + Action act = testSubject.Do; + + // Assert + act.Should().Throw() + .WithMessage("Outer Message*") + .WithInnerException() + .WithMessage("Inner Message"); + } + + [Fact] + public void When_asserting_with_an_aggregate_exception_and_inner_exception_type_from_argument_the_asserts_should_occur_against_the_aggregate_exception() + { + // Arrange + Does testSubject = Does.Throw(new AggregateException("Outer Message", new Exception("Inner Message"))); + + // Act + Action act = testSubject.Do; + + // Assert + act.Should().Throw() + .WithMessage("Outer Message*") + .WithInnerException(typeof(Exception)) + .WithMessage("Inner Message"); + } + } +} From fb0fa80002f44e525a03ab0b615f6096097bbab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Tue, 8 Mar 2022 22:18:00 +0100 Subject: [PATCH 5/9] Rename `Invoking` tests And align content of tests a bit --- .../Exceptions/InvokingActionSpecs.cs | 6 +++--- .../Exceptions/InvokingFunctionSpecs.cs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs index 256205fd28..fc28fc2b1c 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs @@ -6,7 +6,7 @@ namespace FluentAssertions.Specs.Exceptions public class InvokingActionSpecs { [Fact] - public void When_invoking_an_action_on_a_null_subject_it_should_throw() + public void Invoking_on_null_is_not_allowed() { // Arrange Does someClass = null; @@ -20,13 +20,13 @@ public void When_invoking_an_action_on_a_null_subject_it_should_throw() } [Fact] - public void When_invoking_an_action_with_null_it_should_throw() + public void Invoking_with_null_is_not_allowed() { // Arrange Does someClass = Does.NotThrow(); // Act - Action act = () => someClass.Invoking(null).Should().NotThrow(); + Action act = () => someClass.Invoking(null); // Assert act.Should().ThrowExactly() diff --git a/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs index 1a97c41883..f1750ff015 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs @@ -6,13 +6,13 @@ namespace FluentAssertions.Specs.Exceptions public class InvokingFunctionSpecs { [Fact] - public void When_invoking_a_function_on_a_null_subject_it_should_throw() + public void Invoking_on_null_is_not_allowed() { // Arrange Does someClass = null; // Act - Action act = () => someClass.Invoking(d => d.ToString()); + Action act = () => someClass.Invoking(d => d.Return()); // Assert act.Should().ThrowExactly() @@ -20,13 +20,13 @@ public void When_invoking_a_function_on_a_null_subject_it_should_throw() } [Fact] - public void When_invoking_a_function_with_null_it_should_throw() + public void Invoking_with_null_is_not_allowed() { // Arrange - object someClass = new(); + Does someClass = Does.NotThrow(); // Act - Action act = () => someClass.Invoking((Func)null); + Action act = () => someClass.Invoking((Func)null); // Assert act.Should().ThrowExactly() From 524913e1f0a9c993518b36df8164070093204c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Sat, 12 Mar 2022 11:08:36 +0100 Subject: [PATCH 6/9] Split `AssertionScope` specs in multiple files --- .../AssertionScope.ChainingApiSpecs.cs | 421 ++++++++ .../AssertionScope.MessageFormatingSpecs.cs | 566 ++++++++++ .../Execution/AssertionScopeSpecs.cs | 979 +----------------- 3 files changed, 992 insertions(+), 974 deletions(-) create mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs new file mode 100644 index 0000000000..1bf6c1c1df --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs @@ -0,0 +1,421 @@ +using System; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Execution +{ + /// + /// The chaining API specs. + /// + public partial class AssertionScopeSpecs + { + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one() + { + bool succeeded = false; + + // Act + try + { + Execute.Assertion + .ForCondition(condition: true) + .FailWith("First assertion") + .Then + .FailWith("Second assertion"); + } + catch (Exception e) + { + // Assert + succeeded = (e is XunitException xUnitException) && xUnitException.Message.Contains("Second"); + } + + if (!succeeded) + { + throw new XunitException("Expected the second assertion to fail"); + } + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_arguments() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", "assertion"); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_argument_providers() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", () => "assertion"); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_a_fail_reason_function() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith(() => new FailReason("Second {0}", "assertion")); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_continuing_an_assertion_chain_the_reason_should_be_part_of_consecutive_failures() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void When_continuing_an_assertion_chain_the_reason_with_arguments_should_be_part_of_consecutive_failures() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void When_a_given_is_used_before_an_assertion_then_the_result_should_be_available_for_evaluation() + { + // Act + Action act = () => Execute.Assertion + .Given(() => new[] { "a", "b" }) + .ForCondition(collection => collection.Length > 0) + .FailWith("First assertion"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_given_statement() + { + // Arrange + using var _ = new AssertionScope(new IgnoringFailuresAssertionStrategy()); + + // Act / Assert + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .Given(() => throw new InvalidOperationException()); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_condition() + { + // Arrange + bool secondConditionEvaluated = false; + try + { + using var _ = new AssertionScope(); + + // Act + Execute.Assertion + .Given(() => (string)null) + .ForCondition(s => s is not null) + .FailWith("but is was null") + .Then + .ForCondition(_ => secondConditionEvaluated = true) + .FailWith("it should be 42"); + } + catch + { + // Ignore + } + + // Assert + secondConditionEvaluated.Should().BeFalse("because the 2nd condition should not be invoked"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure() + { + // Arrange + using var scope = new AssertionScope(); + + // Act + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .ForCondition(false) + .FailWith("Second assertion"); + + string[] failures = scope.Discard(); + scope.Dispose(); + + Assert.Single(failures); + Assert.Contains("First assertion", failures); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_arguments() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", "assertion"); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_argument_providers() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", () => "assertion"); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_a_fail_reason_function() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith(() => new FailReason("Second {0}", "assertion")); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .WithExpectation("Expectations are the root ") + .ForCondition(false) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the root ") + .FailWith("of all evil"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the root of disappointment"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation_with_arguments() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .WithExpectation("Expectations are the {0} ", "root") + .ForCondition(false) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the {0} ", "root") + .FailWith("of all evil"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the \"root\" of disappointment"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_default_identifier() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .WithDefaultIdentifier("identifier") + .ForCondition(false) + .FailWith("Expected {context}") + .Then + .WithDefaultIdentifier("other") + .FailWith("Expected {context}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected identifier"); + } + + [Fact] + public void When_continuing_a_failed_assertion_chain_consecutive_resons_are_ignored() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .BecauseOf("because {0}", "whatever") + .ForCondition(false) + .FailWith("Expected{reason}") + .Then + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected because whatever"); + } + + [Fact] + public void When_continuing_a_failed_assertion_chain_consecutive_resons_with_arguments_are_ignored() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + Execute.Assertion + .BecauseOf("because {0}", "whatever") + .ForCondition(false) + .FailWith("Expected{reason}") + .Then + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected because whatever"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_evaluate_the_succeeding_given_statement() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(true) + .FailWith("First assertion") + .Then + .Given(() => throw new InvalidOperationException()); + + // Assert + Assert.Throws(act); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the root ") + .ForCondition(true) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the root ") + .FailWith("of all evil"); + + // Assert + act.Should().Throw() + .WithMessage("Assumptions are the root of all evil"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation_with_arguments() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the {0} ", "root") + .ForCondition(true) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the {0} ", "root") + .FailWith("of all evil"); + + // Assert + act.Should().Throw() + .WithMessage("Assumptions are the \"root\" of all evil"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_default_identifier() + { + // Act + Action act = () => + { + Execute.Assertion + .WithDefaultIdentifier("identifier") + .ForCondition(true) + .FailWith("Expected {context}") + .Then + .WithDefaultIdentifier("other") + .FailWith("Expected {context}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected other"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs new file mode 100644 index 0000000000..c7f47d3e4f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs @@ -0,0 +1,566 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Execution +{ + /// + /// The message formatting specs. + /// + public partial class AssertionScopeSpecs + { + [Fact] + public void When_the_same_failure_is_handled_twice_or_more_it_should_still_report_it_once() + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("Failure"); + AssertionScope.Current.FailWith("Failure"); + + using (var nestedScope = new AssertionScope()) + { + nestedScope.FailWith("Failure"); + nestedScope.FailWith("Failure"); + } + + // Act + Action act = scope.Dispose; + + // Assert + try + { + act(); + } + catch (Exception exception) + { + int matches = new Regex(".*Failure.*").Matches(exception.Message).Count; + + matches.Should().Be(4); + } + } + + [Fact] + public void The_failure_message_should_use_the_name_of_the_scope_as_context() + { + // Act + Action act = () => + { + using var _ = new AssertionScope("foo"); + new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected foo to be equal to*"); + } + + [Fact] + public void The_failure_message_should_use_the_lazy_name_of_the_scope_as_context() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(new Lazy(() => "lazy foo")); + new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected lazy foo to be equal to*"); + } + + [Fact] + public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new Dictionary(); + values.Should().ContainKey(0); + values.Should().ContainKey(1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected*to contain key 0*Expected*to contain key 1*"); + } + + [Fact] + public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new List(); + values.Should().ContainSingle(); + values.Should().ContainSingle(); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected*to contain a single item, but the collection is empty*" + + "Expected*to contain a single item, but the collection is empty*"); + } + + [Fact] + public void When_an_assertion_fails_on_BeOfType_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var item = string.Empty; + item.Should().BeOfType(); + item.Should().BeOfType(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected type to be System.Int32, but found System.String.*" + + "Expected type to be System.Int64, but found System.String."); + } + + [Fact] + public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var item = string.Empty; + item.Should().BeAssignableTo(); + item.Should().BeAssignableTo(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected * to be assignable to System.Int32, but System.String is not.*" + + "Expected * to be assignable to System.Int64, but System.String is not."); + } + + [Fact] + public void When_parentheses_are_used_in_the_because_arguments_it_should_render_them_correctly() + { + // Act + Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}"); + + // Assert + act.Should().Throw() + .WithMessage("*because can't use these in becauseArgs: { }*"); + } + + [Fact] + public void When_becauseArgs_is_null_it_should_render_reason_correctly() + { + // Act + object[] becauseArgs = null; + Action act = () => 1.Should().Be(2, "it should still work", becauseArgs); + + // Assert + act.Should().Throw() + .WithMessage("*it should still work*"); + } + + [Fact] + public void When_invalid_format_is_used_in_because_parameter_without_becauseArgs_it_should_still_render_reason_correctly() + { + // Act + Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because parameters"); + + // Assert + act.Should().Throw() + .WithMessage("*because use of {} is okay if there are no because parameters*"); + } + + [Fact] + public void When_invalid_format_is_used_in_because_parameter_along_with_becauseArgs_it_should_render_default_text() + { + // Act + Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", "additional becauseArgs parameter"); + + // Assert + act.Should().Throw() + .WithMessage("*because message 'use of {} is considered invalid in because parameter with becauseArgs' could not be formatted with string.Format*"); + } + + [Fact] + public void When_an_assertion_fails_in_a_scope_with_braces_it_should_use_the_name_as_the_assertion_context() + { + // Act + Action act = () => + { + using var _ = new AssertionScope("{}"); + default(int[]).Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected {} to be equal to*"); + } + + [Fact] + public void When_parentheses_are_used_in_literal_values_it_should_render_them_correctly() + { + // Act + Action act = () => "{foo}".Should().Be("{bar}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected string to be \"{bar}\", but \"{foo}\" differs near*"); + } + + [Fact] + public void When_message_contains_double_braces_they_should_not_be_replaced_with_context() + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("{{empty}}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("*empty*"); + } + + [InlineData("\r")] + [InlineData("\\r")] + [InlineData("\\\r")] + [InlineData("\\\\r")] + [InlineData("\\\\\r")] + [Theory] + public void When_message_contains_backslash_followed_by_r_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith(str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage(str); + } + + [InlineData("\r")] + [InlineData("\\r")] + [InlineData("\\\r")] + [InlineData("\\\\r")] + [InlineData("\\\\\r")] + [Theory] + public void When_message_argument_contains_backslash_followed_by_r_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("\\{0}\\A", str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("\\\"" + str + "\"\\A*"); + } + + [InlineData("\n")] + [InlineData("\\n")] + [InlineData("\\\n")] + [InlineData("\\\\n")] + [InlineData("\\\\\n")] + [Theory] + public void When_message_contains_backslash_followed_by_n_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith(str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage(str); + } + + [InlineData("\n")] + [InlineData("\\n")] + [InlineData("\\\n")] + [InlineData("\\\\n")] + [InlineData("\\\\\n")] + [Theory] + public void When_message_argument_contains_backslash_followed_by_n_is_should_format_correctly(string str) + { + // Arrange + var scope = new AssertionScope(); + + AssertionScope.Current.FailWith("\\{0}\\A", str); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("\\\"" + str + "\"\\A*"); + } + + [Fact] + public void When_subject_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() + { + // Arrange / Act + Action act = () => "A\\".Should().Be("A"); + + // Assert + act.Should().Throw() + .WithMessage(@"* near ""\"" *", "trailing backslashes should not be removed from failure message"); + } + + [Fact] + public void When_expectation_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() + { + // Arrange / Act + Action act = () => "A".Should().Be("A\\"); + + // Assert + act.Should().Throw() + .WithMessage(@"* to be ""A\"" *", "trailing backslashes should not be removed from failure message"); + } + + [Fact] + public void When_message_starts_with_single_braces_they_should_be_replaced_with_context() + { + // Arrange + var scope = new AssertionScope(); + scope.AddReportable("MyKey", "MyValue"); + + AssertionScope.Current.FailWith("{MyKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("MyValue*"); + } + + [Fact] + public void When_message_starts_with_two_single_braces_they_should_be_replaced_with_context() + { + // Arrange + var scope = new AssertionScope(); + scope.AddReportable("SomeKey", "SomeValue"); + scope.AddReportable("AnotherKey", "AnotherValue"); + + AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("SomeValueAnotherValue*"); + } + + [Fact] + public void When_adding_reportable_values_they_should_be_reported_after_the_message() + { + // Arrange + var scope = new AssertionScope(); + scope.AddReportable("SomeKey", "SomeValue"); + scope.AddReportable("AnotherKey", "AnotherValue"); + + AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("*With SomeKey:\nSomeValue\nWith AnotherKey:\nAnotherValue"); + } + + [Fact] + public void When_adding_non_reportable_value_it_should_not_be_reported_after_the_message() + { + // Arrange + var scope = new AssertionScope(); + scope.AddNonReportable("SomeKey", "SomeValue"); + + AssertionScope.Current.FailWith("{SomeKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .Which.Message.Should().NotContain("With SomeKey:\nSomeValue"); + } + + [Fact] + public void When_adding_non_reportable_value_it_should_be_retrievable_from_context() + { + // Arrange + var scope = new AssertionScope(); + scope.AddNonReportable("SomeKey", "SomeValue"); + + // Act + var value = scope.Get("SomeKey"); + + // Assert + value.Should().Be("SomeValue"); + } + + [Fact] + public void When_using_a_deferred_reportable_value_it_is_not_calculated_if_there_are_no_failures() + { + // Arrange + var scope = new AssertionScope(); + var deferredValueInvoked = false; + + scope.AddReportable("MyKey", () => + { + deferredValueInvoked = true; + + return "MyValue"; + }); + + // Act + scope.Dispose(); + + // Assert + deferredValueInvoked.Should().BeFalse(); + } + + [Fact] + public void When_using_a_deferred_reportable_value_it_is_calculated_if_there_is_a_failure() + { + // Arrange + var scope = new AssertionScope(); + var deferredValueInvoked = false; + + scope.AddReportable("MyKey", () => + { + deferredValueInvoked = true; + + return "MyValue"; + }); + + AssertionScope.Current.FailWith("{MyKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("*MyValue*"); + deferredValueInvoked.Should().BeTrue(); + } + + [Fact] + public void When_an_expectation_is_defined_it_should_be_preceeding_the_failure_message() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the root ") + .ForCondition(false) + .FailWith("of disappointment"); + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the root of disappointment"); + } + + [Fact] + public void When_an_expectation_with_arguments_is_defined_it_should_be_preceeding_the_failure_message() + { + // Act + Action act = () => Execute.Assertion + .WithExpectation("Expectations are the {0} ", "root") + .ForCondition(false) + .FailWith("of disappointment"); + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the \"root\" of disappointment"); + } + + [Fact] + public void When_no_identifier_can_be_resolved_replace_context_with_object() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(false) + .FailWith("Expected {context}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected object"); + } + + [Fact] + public void When_no_identifier_can_be_resolved_replace_context_with_inline_declared_fallback_identifier() + { + // Act + Action act = () => Execute.Assertion + .ForCondition(false) + .FailWith("Expected {context:fallback}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected fallback"); + } + + [Fact] + public void When_no_identifier_can_be_resolved_replace_context_with_defined_default_identifier() + { + // Act + Action act = () => Execute.Assertion + .WithDefaultIdentifier("identifier") + .ForCondition(false) + .FailWith("Expected {context}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected identifier"); + } + + [Fact] + public void The_failure_message_should_contain_the_reason() + { + // Act + Action act = () => Execute.Assertion + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void The_failure_message_should_contain_the_reason_with_arguments() + { + // Act + Action act = () => Execute.Assertion + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs index d33c78265a..5d189c8e18 100644 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -14,10 +13,11 @@ namespace FluentAssertions.Specs.Execution { - public class AssertionScopeSpecs + /// + /// Type specs. + /// + public partial class AssertionScopeSpecs { - #region Lifecycle Management - [Fact] public void When_disposed_it_should_throw_any_failures() { @@ -240,7 +240,7 @@ public void When_using_a_custom_strategy_it_should_include_failure_messages_of_a public class CustomAssertionStrategy : IAssertionStrategy { - private readonly List failureMessages = new List(); + private readonly List failureMessages = new(); public IEnumerable FailureMessages => failureMessages; @@ -290,975 +290,6 @@ public void ThrowIfAny(IDictionary context) // do nothing } } - - #endregion - - #region Message Formatting - - [Fact] - public void When_the_same_failure_is_handled_twice_or_more_it_should_still_report_it_once() - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("Failure"); - AssertionScope.Current.FailWith("Failure"); - - using (var nestedScope = new AssertionScope()) - { - nestedScope.FailWith("Failure"); - nestedScope.FailWith("Failure"); - } - - // Act - Action act = scope.Dispose; - - // Assert - try - { - act(); - } - catch (Exception exception) - { - int matches = new Regex(".*Failure.*").Matches(exception.Message).Count; - - matches.Should().Be(4); - } - } - - [Fact] - public void The_failure_message_should_use_the_name_of_the_scope_as_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope("foo"); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected foo to be equal to*"); - } - - [Fact] - public void The_failure_message_should_use_the_lazy_name_of_the_scope_as_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(new Lazy(() => "lazy foo")); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected lazy foo to be equal to*"); - } - - [Fact] - public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var values = new Dictionary(); - values.Should().ContainKey(0); - values.Should().ContainKey(1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected*to contain key 0*Expected*to contain key 1*"); - } - - [Fact] - public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var values = new List(); - values.Should().ContainSingle(); - values.Should().ContainSingle(); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected*to contain a single item, but the collection is empty*" + - "Expected*to contain a single item, but the collection is empty*"); - } - - [Fact] - public void When_an_assertion_fails_on_BeOfType_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var item = string.Empty; - item.Should().BeOfType(); - item.Should().BeOfType(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected type to be System.Int32, but found System.String.*" + - "Expected type to be System.Int64, but found System.String."); - } - - [Fact] - public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var item = string.Empty; - item.Should().BeAssignableTo(); - item.Should().BeAssignableTo(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected * to be assignable to System.Int32, but System.String is not.*" + - "Expected * to be assignable to System.Int64, but System.String is not."); - } - - [Fact] - public void When_parentheses_are_used_in_the_because_arguments_it_should_render_them_correctly() - { - // Act - Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}"); - - // Assert - act.Should().Throw() - .WithMessage("*because can't use these in becauseArgs: { }*"); - } - - [Fact] - public void When_becauseArgs_is_null_it_should_render_reason_correctly() - { - // Act - object[] becauseArgs = null; - Action act = () => 1.Should().Be(2, "it should still work", becauseArgs); - - // Assert - act.Should().Throw() - .WithMessage("*it should still work*"); - } - - [Fact] - public void When_invalid_format_is_used_in_because_parameter_without_becauseArgs_it_should_still_render_reason_correctly() - { - // Act - Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because parameters"); - - // Assert - act.Should().Throw() - .WithMessage("*because use of {} is okay if there are no because parameters*"); - } - - [Fact] - public void When_invalid_format_is_used_in_because_parameter_along_with_becauseArgs_it_should_render_default_text() - { - // Act - Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", "additional becauseArgs parameter"); - - // Assert - act.Should().Throw() - .WithMessage("*because message 'use of {} is considered invalid in because parameter with becauseArgs' could not be formatted with string.Format*"); - } - - [Fact] - public void When_an_assertion_fails_in_a_scope_with_braces_it_should_use_the_name_as_the_assertion_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope("{}"); - default(int[]).Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected {} to be equal to*"); - } - - [Fact] - public void When_parentheses_are_used_in_literal_values_it_should_render_them_correctly() - { - // Act - Action act = () => "{foo}".Should().Be("{bar}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected string to be \"{bar}\", but \"{foo}\" differs near*"); - } - - [Fact] - public void When_message_contains_double_braces_they_should_not_be_replaced_with_context() - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("{{empty}}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("*empty*"); - } - - [InlineData("\r")] - [InlineData("\\r")] - [InlineData("\\\r")] - [InlineData("\\\\r")] - [InlineData("\\\\\r")] - [Theory] - public void When_message_contains_backslash_followed_by_r_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith(str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage(str); - } - - [InlineData("\r")] - [InlineData("\\r")] - [InlineData("\\\r")] - [InlineData("\\\\r")] - [InlineData("\\\\\r")] - [Theory] - public void When_message_argument_contains_backslash_followed_by_r_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("\\{0}\\A", str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("\\\"" + str + "\"\\A*"); - } - - [InlineData("\n")] - [InlineData("\\n")] - [InlineData("\\\n")] - [InlineData("\\\\n")] - [InlineData("\\\\\n")] - [Theory] - public void When_message_contains_backslash_followed_by_n_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith(str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage(str); - } - - [InlineData("\n")] - [InlineData("\\n")] - [InlineData("\\\n")] - [InlineData("\\\\n")] - [InlineData("\\\\\n")] - [Theory] - public void When_message_argument_contains_backslash_followed_by_n_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("\\{0}\\A", str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("\\\"" + str + "\"\\A*"); - } - - [Fact] - public void When_subject_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() - { - // Arrange / Act - Action act = () => "A\\".Should().Be("A"); - - // Assert - act.Should().Throw() - .WithMessage(@"* near ""\"" *", "trailing backslashes should not be removed from failure message"); - } - - [Fact] - public void When_expectation_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() - { - // Arrange / Act - Action act = () => "A".Should().Be("A\\"); - - // Assert - act.Should().Throw() - .WithMessage(@"* to be ""A\"" *", "trailing backslashes should not be removed from failure message"); - } - - [Fact] - public void When_message_starts_with_single_braces_they_should_be_replaced_with_context() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("MyKey", "MyValue"); - - AssertionScope.Current.FailWith("{MyKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("MyValue*"); - } - - [Fact] - public void When_message_starts_with_two_single_braces_they_should_be_replaced_with_context() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("SomeKey", "SomeValue"); - scope.AddReportable("AnotherKey", "AnotherValue"); - - AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("SomeValueAnotherValue*"); - } - - [Fact] - public void When_adding_reportable_values_they_should_be_reported_after_the_message() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("SomeKey", "SomeValue"); - scope.AddReportable("AnotherKey", "AnotherValue"); - - AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("*With SomeKey:\nSomeValue\nWith AnotherKey:\nAnotherValue"); - } - - [Fact] - public void When_adding_non_reportable_value_it_should_not_be_reported_after_the_message() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - - AssertionScope.Current.FailWith("{SomeKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .Which.Message.Should().NotContain("With SomeKey:\nSomeValue"); - } - - [Fact] - public void When_adding_non_reportable_value_it_should_be_retrievable_from_context() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - - // Act - var value = scope.Get("SomeKey"); - - // Assert - value.Should().Be("SomeValue"); - } - - [Fact] - public void When_using_a_deferred_reportable_value_it_is_not_calculated_if_there_are_no_failures() - { - // Arrange - var scope = new AssertionScope(); - var deferredValueInvoked = false; - - scope.AddReportable("MyKey", () => - { - deferredValueInvoked = true; - - return "MyValue"; - }); - - // Act - scope.Dispose(); - - // Assert - deferredValueInvoked.Should().BeFalse(); - } - - [Fact] - public void When_using_a_deferred_reportable_value_it_is_calculated_if_there_is_a_failure() - { - // Arrange - var scope = new AssertionScope(); - var deferredValueInvoked = false; - - scope.AddReportable("MyKey", () => - { - deferredValueInvoked = true; - - return "MyValue"; - }); - - AssertionScope.Current.FailWith("{MyKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("*MyValue*"); - deferredValueInvoked.Should().BeTrue(); - } - - [Fact] - public void When_an_expectation_is_defined_it_should_be_preceeding_the_failure_message() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(false) - .FailWith("of disappointment"); - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the root of disappointment"); - } - - [Fact] - public void When_an_expectation_with_arguments_is_defined_it_should_be_preceeding_the_failure_message() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(false) - .FailWith("of disappointment"); - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the \"root\" of disappointment"); - } - - [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_object() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(false) - .FailWith("Expected {context}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected object"); - } - - [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_inline_declared_fallback_identifier() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(false) - .FailWith("Expected {context:fallback}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected fallback"); - } - - [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_defined_default_identifier() - { - // Act - Action act = () => Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(false) - .FailWith("Expected {context}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected identifier"); - } - - [Fact] - public void The_failure_message_should_contain_the_reason() - { - // Act - Action act = () => Execute.Assertion - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void The_failure_message_should_contain_the_reason_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - #endregion - - #region Chaining API - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one() - { - bool succeeded = false; - - // Act - try - { - Execute.Assertion - .ForCondition(condition: true) - .FailWith("First assertion") - .Then - .FailWith("Second assertion"); - } - catch (Exception e) - { - // Assert - succeeded = (e is XunitException xUnitException) && xUnitException.Message.Contains("Second"); - } - - if (!succeeded) - { - throw new XunitException("Expected the second assertion to fail"); - } - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_argument_providers() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", () => "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_a_fail_reason_function() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith(() => new FailReason("Second {0}", "assertion")); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_continuing_an_assertion_chain_the_reason_should_be_part_of_consecutive_failures() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void When_continuing_an_assertion_chain_the_reason_with_arguments_should_be_part_of_consecutive_failures() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void When_a_given_is_used_before_an_assertion_then_the_result_should_be_available_for_evaluation() - { - // Act - Action act = () => Execute.Assertion - .Given(() => new[] { "a", "b" }) - .ForCondition(collection => collection.Length > 0) - .FailWith("First assertion"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_given_statement() - { - // Arrange - using var _ = new AssertionScope(new IgnoringFailuresAssertionStrategy()); - - // Act / Assert - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .Given(() => throw new InvalidOperationException()); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_condition() - { - // Arrange - bool secondConditionEvaluated = false; - try - { - using var _ = new AssertionScope(); - - // Act - Execute.Assertion - .Given(() => (string)null) - .ForCondition(s => s is not null) - .FailWith("but is was null") - .Then - .ForCondition(s => secondConditionEvaluated = true) - .FailWith("it should be 42"); - } - catch - { - // Ignore - } - - // Assert - secondConditionEvaluated.Should().BeFalse("because the 2nd condition should not be invoked"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure() - { - // Arrange - using var scope = new AssertionScope(); - - // Act - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .ForCondition(false) - .FailWith("Second assertion"); - - string[] failures = scope.Discard(); - scope.Dispose(); - - Assert.Single(failures); - Assert.Contains("First assertion", failures); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_arguments() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", "assertion"); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_argument_providers() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", () => "assertion"); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_a_fail_reason_function() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith(() => new FailReason("Second {0}", "assertion")); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(false) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the root ") - .FailWith("of all evil"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the root of disappointment"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation_with_arguments() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(false) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the {0} ", "root") - .FailWith("of all evil"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the \"root\" of disappointment"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_default_identifier() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(false) - .FailWith("Expected {context}") - .Then - .WithDefaultIdentifier("other") - .FailWith("Expected {context}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected identifier"); - } - - [Fact] - public void When_continuing_a_failed_assertion_chain_consecutive_resons_are_ignored() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .BecauseOf("because {0}", "whatever") - .ForCondition(false) - .FailWith("Expected{reason}") - .Then - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected because whatever"); - } - - [Fact] - public void When_continuing_a_failed_assertion_chain_consecutive_resons_with_arguments_are_ignored() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - Execute.Assertion - .BecauseOf("because {0}", "whatever") - .ForCondition(false) - .FailWith("Expected{reason}") - .Then - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected because whatever"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_evaluate_the_succeeding_given_statement() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .Given(() => throw new InvalidOperationException()); - - // Assert - Assert.Throws(act); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(true) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the root ") - .FailWith("of all evil"); - - // Assert - act.Should().Throw() - .WithMessage("Assumptions are the root of all evil"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(true) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the {0} ", "root") - .FailWith("of all evil"); - - // Assert - act.Should().Throw() - .WithMessage("Assumptions are the \"root\" of all evil"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_default_identifier() - { - // Act - Action act = () => - { - Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(true) - .FailWith("Expected {context}") - .Then - .WithDefaultIdentifier("other") - .FailWith("Expected {context}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected other"); - } - - #endregion } } From 921e7029f75d736e8888183b924836ce7b8fe7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Sun, 13 Mar 2022 21:21:41 +0100 Subject: [PATCH 7/9] Add tests to appropriate files --- .../CollectionAssertionSpecs.ContainSingle.cs | 20 ++++++++++++++++--- .../GenericDictionaryAssertionSpecs.cs | 19 ++++++++++++++++++ .../Primitives/ObjectAssertionSpecs.cs | 19 ++++++++++++++++++ .../ReferenceTypeAssertionsSpecs.cs | 19 ++++++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs index 81a0eda408..305f988fd6 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs @@ -13,8 +13,6 @@ namespace FluentAssertions.Specs.Collections /// public partial class CollectionAssertionSpecs { - #region Contain Single - [Fact] public void When_injecting_a_null_predicate_into_ContainSingle_it_should_throw() { @@ -272,6 +270,22 @@ public void When_collection_is_IEnumerable_it_should_be_evaluated_only_once() act.Should().NotThrow(); } - #endregion + [Fact] + public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new List(); + values.Should().ContainSingle(); + values.Should().ContainSingle(); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected*to contain a single item, but the collection is empty*" + + "Expected*to contain a single item, but the collection is empty*"); + } } } diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs index 86c451cf7d..5d51b4d959 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -1570,6 +1571,24 @@ public void When_a_dictionary_checks_a_list_of_keys_not_to_be_present_it_will_ho // Assert act.Should().Throw(); } + + [Fact] + public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new Dictionary(); + values.Should().ContainKey(0); + values.Should().ContainKey(1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected*to contain key 0*Expected*to contain key 1*"); + } + #endregion #region ContainValue diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs index f1e17548ce..6ac03c28bf 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs @@ -632,6 +632,25 @@ public void When_unrelated_to_open_generic_type_it_should_fail_with_a_descriptiv .WithMessage($"*assignable to {typeof(System.Collections.Generic.IList<>)}*failure message*{typeof(DummyImplementingClass)} is not*"); } + [Fact] + public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var item = string.Empty; + item.Should().BeAssignableTo(); + item.Should().BeAssignableTo(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected * to be assignable to System.Int32, but System.String is not.*" + + "Expected * to be assignable to System.Int64, but System.String is not."); + } + #endregion #region NotBeAssignableTo diff --git a/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs index 4e78d052bc..22802e50f3 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs @@ -142,6 +142,25 @@ public void When_object_is_not_of_the_expected_type_it_should_throw() .WithMessage("Expected type to be System.Int32, but found System.String."); } + [Fact] + public void When_an_assertion_fails_on_BeOfType_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var item = string.Empty; + item.Should().BeOfType(); + item.Should().BeOfType(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected type to be System.Int32, but found System.String.*" + + "Expected type to be System.Int64, but found System.String."); + } + [Fact] public void When_object_is_of_the_unexpected_type_it_should_throw() { From ff23b0a1e2f46670e78376718459c1c3c873aa18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Sat, 12 Mar 2022 12:25:28 +0100 Subject: [PATCH 8/9] Updated test names, combined, and removed some tests --- .../AssertionScope.MessageFormatingSpecs.cs | 184 +++++------------- 1 file changed, 52 insertions(+), 132 deletions(-) diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs index c7f47d3e4f..a7a606db12 100644 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs @@ -43,23 +43,25 @@ public void When_the_same_failure_is_handled_twice_or_more_it_should_still_repor } } - [Fact] - public void The_failure_message_should_use_the_name_of_the_scope_as_context() + [InlineData("foo")] + [InlineData("{}")] + [Theory] + public void Message_should_use_the_name_of_the_scope_as_context(string context) { // Act Action act = () => { - using var _ = new AssertionScope("foo"); + using var _ = new AssertionScope(context); new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); }; // Assert act.Should().Throw() - .WithMessage("Expected foo to be equal to*"); + .WithMessage($"Expected {context} to be equal to*"); } [Fact] - public void The_failure_message_should_use_the_lazy_name_of_the_scope_as_context() + public void Message_should_use_the_lazy_name_of_the_scope_as_context() { // Act Action act = () => @@ -74,7 +76,7 @@ public void The_failure_message_should_use_the_lazy_name_of_the_scope_as_context } [Fact] - public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() + public void Message_should_contain_each_unique_failed_assertion_seperately() { // Act Action act = () => @@ -87,11 +89,13 @@ public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_i // Assert act.Should().Throw() - .WithMessage("Expected*to contain key 0*Expected*to contain key 1*"); + .WithMessage( + "Expected * to contain key 0.\n" + + "Expected * to contain key 1.\n"); } [Fact] - public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_be_included() + public void Message_should_contain_the_same_failed_assertion_seperately_if_called_multiple_times() { // Act Action act = () => @@ -102,52 +106,15 @@ public void When_an_assertion_fails_on_ContainSingle_succeeding_message_should_b values.Should().ContainSingle(); }; - // Assert - act.Should().Throw() - .WithMessage("Expected*to contain a single item, but the collection is empty*" + - "Expected*to contain a single item, but the collection is empty*"); - } - - [Fact] - public void When_an_assertion_fails_on_BeOfType_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var item = string.Empty; - item.Should().BeOfType(); - item.Should().BeOfType(); - }; - // Assert act.Should().Throw() .WithMessage( - "Expected type to be System.Int32, but found System.String.*" + - "Expected type to be System.Int64, but found System.String."); + "Expected * to contain a single item, but the collection is empty.\n" + + "Expected * to contain a single item, but the collection is empty.\n"); } [Fact] - public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var item = string.Empty; - item.Should().BeAssignableTo(); - item.Should().BeAssignableTo(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected * to be assignable to System.Int32, but System.String is not.*" + - "Expected * to be assignable to System.Int64, but System.String is not."); - } - - [Fact] - public void When_parentheses_are_used_in_the_because_arguments_it_should_render_them_correctly() + public void Because_reason_should_keep_parentheses_in_arguments_as_literals() { // Act Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}"); @@ -158,7 +125,7 @@ public void When_parentheses_are_used_in_the_because_arguments_it_should_render_ } [Fact] - public void When_becauseArgs_is_null_it_should_render_reason_correctly() + public void Because_reason_should_ignore_undefined_arguments() { // Act object[] becauseArgs = null; @@ -166,25 +133,25 @@ public void When_becauseArgs_is_null_it_should_render_reason_correctly() // Assert act.Should().Throw() - .WithMessage("*it should still work*"); + .WithMessage("*because it should still work*"); } [Fact] - public void When_invalid_format_is_used_in_because_parameter_without_becauseArgs_it_should_still_render_reason_correctly() + public void Because_reason_should_threat_parentheses_as_literals_if_no_arguments_are_defined() { // Act - Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because parameters"); + Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because arguments"); // Assert act.Should().Throw() - .WithMessage("*because use of {} is okay if there are no because parameters*"); + .WithMessage("*because use of {} is okay if there are no because arguments*"); } [Fact] - public void When_invalid_format_is_used_in_because_parameter_along_with_becauseArgs_it_should_render_default_text() + public void Because_reason_should_inform_about_invalid_parentheses_with_a_default_message() { // Act - Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", "additional becauseArgs parameter"); + Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", "additional becauseArgs argument"); // Assert act.Should().Throw() @@ -192,22 +159,7 @@ public void When_invalid_format_is_used_in_because_parameter_along_with_becauseA } [Fact] - public void When_an_assertion_fails_in_a_scope_with_braces_it_should_use_the_name_as_the_assertion_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope("{}"); - default(int[]).Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected {} to be equal to*"); - } - - [Fact] - public void When_parentheses_are_used_in_literal_values_it_should_render_them_correctly() + public void Message_should_keep_parentheses_in_literal_values() { // Act Action act = () => "{foo}".Should().Be("{bar}"); @@ -218,10 +170,10 @@ public void When_parentheses_are_used_in_literal_values_it_should_render_them_co } [Fact] - public void When_message_contains_double_braces_they_should_not_be_replaced_with_context() + public void Message_should_contain_literal_value_if_marked_with_double_parentheses() { // Arrange - var scope = new AssertionScope(); + var scope = new AssertionScope("context"); AssertionScope.Current.FailWith("{{empty}}"); @@ -230,7 +182,7 @@ public void When_message_contains_double_braces_they_should_not_be_replaced_with // Assert act.Should().ThrowExactly() - .WithMessage("*empty*"); + .WithMessage("{empty}*"); } [InlineData("\r")] @@ -238,50 +190,13 @@ public void When_message_contains_double_braces_they_should_not_be_replaced_with [InlineData("\\\r")] [InlineData("\\\\r")] [InlineData("\\\\\r")] - [Theory] - public void When_message_contains_backslash_followed_by_r_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith(str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage(str); - } - - [InlineData("\r")] - [InlineData("\\r")] - [InlineData("\\\r")] - [InlineData("\\\\r")] - [InlineData("\\\\\r")] - [Theory] - public void When_message_argument_contains_backslash_followed_by_r_is_should_format_correctly(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("\\{0}\\A", str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("\\\"" + str + "\"\\A*"); - } - [InlineData("\n")] [InlineData("\\n")] [InlineData("\\\n")] [InlineData("\\\\n")] [InlineData("\\\\\n")] [Theory] - public void When_message_contains_backslash_followed_by_n_is_should_format_correctly(string str) + public void Message_should_not_have_modified_carriage_return_or_line_feed_control_characters(string str) { // Arrange var scope = new AssertionScope(); @@ -296,13 +211,18 @@ public void When_message_contains_backslash_followed_by_n_is_should_format_corre .WithMessage(str); } + [InlineData("\r")] + [InlineData("\\r")] + [InlineData("\\\r")] + [InlineData("\\\\r")] + [InlineData("\\\\\r")] [InlineData("\n")] [InlineData("\\n")] [InlineData("\\\n")] [InlineData("\\\\n")] [InlineData("\\\\\n")] [Theory] - public void When_message_argument_contains_backslash_followed_by_n_is_should_format_correctly(string str) + public void Message_should_not_have_modified_carriage_return_or_line_feed_control_characters_in_supplied_arguments(string str) { // Arrange var scope = new AssertionScope(); @@ -318,29 +238,29 @@ public void When_message_argument_contains_backslash_followed_by_n_is_should_for } [Fact] - public void When_subject_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() + public void Message_should_not_have_trailing_backslashes_removed_from_subject() { // Arrange / Act Action act = () => "A\\".Should().Be("A"); // Assert act.Should().Throw() - .WithMessage(@"* near ""\"" *", "trailing backslashes should not be removed from failure message"); + .WithMessage(@"* near ""\"" *"); } [Fact] - public void When_expectation_has_trailing_backslash_the_failure_message_should_contain_the_trailing_backslash() + public void Message_should_not_have_trailing_backslashes_removed_from_expectation() { // Arrange / Act Action act = () => "A".Should().Be("A\\"); // Assert act.Should().Throw() - .WithMessage(@"* to be ""A\"" *", "trailing backslashes should not be removed from failure message"); + .WithMessage(@"* to be ""A\"" *"); } [Fact] - public void When_message_starts_with_single_braces_they_should_be_replaced_with_context() + public void Message_should_have_named_placeholder_be_replaced_by_reportable_value() { // Arrange var scope = new AssertionScope(); @@ -357,7 +277,7 @@ public void When_message_starts_with_single_braces_they_should_be_replaced_with_ } [Fact] - public void When_message_starts_with_two_single_braces_they_should_be_replaced_with_context() + public void Message_should_have_named_placeholders_be_replaced_by_reportable_values() { // Arrange var scope = new AssertionScope(); @@ -375,7 +295,7 @@ public void When_message_starts_with_two_single_braces_they_should_be_replaced_w } [Fact] - public void When_adding_reportable_values_they_should_be_reported_after_the_message() + public void Message_should_have_reportable_values_appended_at_the_end() { // Arrange var scope = new AssertionScope(); @@ -393,7 +313,7 @@ public void When_adding_reportable_values_they_should_be_reported_after_the_mess } [Fact] - public void When_adding_non_reportable_value_it_should_not_be_reported_after_the_message() + public void Message_should_not_have_nonreportable_values_appended_at_the_end() { // Arrange var scope = new AssertionScope(); @@ -410,7 +330,7 @@ public void When_adding_non_reportable_value_it_should_not_be_reported_after_the } [Fact] - public void When_adding_non_reportable_value_it_should_be_retrievable_from_context() + public void Message_should_have_named_placeholder_be_replaced_by_nonreportable_value() { // Arrange var scope = new AssertionScope(); @@ -424,7 +344,7 @@ public void When_adding_non_reportable_value_it_should_be_retrievable_from_conte } [Fact] - public void When_using_a_deferred_reportable_value_it_is_not_calculated_if_there_are_no_failures() + public void Deferred_reportable_values_should_not_be_calculated_in_absence_of_failures() { // Arrange var scope = new AssertionScope(); @@ -445,7 +365,7 @@ public void When_using_a_deferred_reportable_value_it_is_not_calculated_if_there } [Fact] - public void When_using_a_deferred_reportable_value_it_is_calculated_if_there_is_a_failure() + public void Message_should_have_named_placeholder_be_replaced_by_defered_reportable_value() { // Arrange var scope = new AssertionScope(); @@ -465,12 +385,12 @@ public void When_using_a_deferred_reportable_value_it_is_calculated_if_there_is_ // Assert act.Should().ThrowExactly() - .WithMessage("*MyValue*"); + .WithMessage("MyValue*"); deferredValueInvoked.Should().BeTrue(); } [Fact] - public void When_an_expectation_is_defined_it_should_be_preceeding_the_failure_message() + public void Message_should_start_with_the_defined_expectation() { // Act Action act = () => Execute.Assertion @@ -484,7 +404,7 @@ public void When_an_expectation_is_defined_it_should_be_preceeding_the_failure_m } [Fact] - public void When_an_expectation_with_arguments_is_defined_it_should_be_preceeding_the_failure_message() + public void Message_should_start_with_the_defined_expectation_and_arguments() { // Act Action act = () => Execute.Assertion @@ -498,7 +418,7 @@ public void When_an_expectation_with_arguments_is_defined_it_should_be_preceedin } [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_object() + public void Message_should_contain_object_as_context_if_identifier_can_not_be_resolved() { // Act Action act = () => Execute.Assertion @@ -511,7 +431,7 @@ public void When_no_identifier_can_be_resolved_replace_context_with_object() } [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_inline_declared_fallback_identifier() + public void Message_should_contain_the_fallback_value_as_context_if_identifier_can_not_be_resolved() { // Act Action act = () => Execute.Assertion @@ -524,7 +444,7 @@ public void When_no_identifier_can_be_resolved_replace_context_with_inline_decla } [Fact] - public void When_no_identifier_can_be_resolved_replace_context_with_defined_default_identifier() + public void Message_should_contain_the_default_identifier_as_context_if_identifier_can_not_be_resolved() { // Act Action act = () => Execute.Assertion @@ -538,7 +458,7 @@ public void When_no_identifier_can_be_resolved_replace_context_with_defined_defa } [Fact] - public void The_failure_message_should_contain_the_reason() + public void Message_should_contain_the_reason_as_defined() { // Act Action act = () => Execute.Assertion @@ -551,7 +471,7 @@ public void The_failure_message_should_contain_the_reason() } [Fact] - public void The_failure_message_should_contain_the_reason_with_arguments() + public void Message_should_contain_the_reason_as_defined_with_arguments() { // Act Action act = () => Execute.Assertion From b146a4a251f8c79a4e1340037f19b62f41889f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Hompus?= Date: Sat, 12 Mar 2022 13:57:05 +0100 Subject: [PATCH 9/9] Remove some mutants --- .../AssertionScope.ContextDataSpecs.cs | 69 +++++++++++++++++++ .../AssertionScope.MessageFormatingSpecs.cs | 9 ++- 2 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs new file mode 100644 index 0000000000..7d6c207278 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs @@ -0,0 +1,69 @@ +using FluentAssertions.Execution; +using Xunit; + +namespace FluentAssertions.Specs.Execution +{ + /// + /// The chaining API specs. + /// + public partial class AssertionScopeSpecs + { + [Fact] + public void Get_value_when_key_is_present() + { + // Arrange + var scope = new AssertionScope(); + scope.AddNonReportable("SomeKey", "SomeValue"); + scope.AddNonReportable("SomeOtherKey", "SomeOtherValue"); + + // Act + var value = scope.Get("SomeKey"); + + // Assert + value.Should().Be("SomeValue"); + } + + [Fact] + public void Get_default_value_when_key_is_not_present() + { + // Arrange + var scope = new AssertionScope(); + + // Act + var value = scope.Get("SomeKey"); + + // Assert + value.Should().Be(0); + } + + [Fact] + public void Get_default_value_when_nullable_value_is_null() + { + // Arrange + var scope = new AssertionScope(); +#pragma warning disable IDE0004 // Remove Unnecessary Cast + scope.AddNonReportable("SomeKey", (int?)null); +#pragma warning restore IDE0004 // Remove Unnecessary Cast + + // Act + var value = scope.Get("SomeKey"); + + // Assert + value.Should().Be(0); + } + + [Fact] + public void Value_should_be_of_requested_type() + { + // Arrange + var scope = new AssertionScope(); + scope.AddNonReportable("SomeKey", "SomeValue"); + + // Act + var value = scope.Get("SomeKey"); + + // Assert + value.Should().BeOfType(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs index a7a606db12..1cc884c9c8 100644 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs @@ -336,11 +336,14 @@ public void Message_should_have_named_placeholder_be_replaced_by_nonreportable_v var scope = new AssertionScope(); scope.AddNonReportable("SomeKey", "SomeValue"); + AssertionScope.Current.FailWith("{SomeKey}"); + // Act - var value = scope.Get("SomeKey"); + Action act = scope.Dispose; // Assert - value.Should().Be("SomeValue"); + act.Should().ThrowExactly() + .WithMessage("SomeValue"); } [Fact] @@ -385,7 +388,7 @@ public void Message_should_have_named_placeholder_be_replaced_by_defered_reporta // Assert act.Should().ThrowExactly() - .WithMessage("MyValue*"); + .WithMessage("MyValue*\n\nWith MyKey:\nMyValue\n"); deferredValueInvoked.Should().BeTrue(); }