From 571c69fa1e4eae3f4ac61b976f9178df422dc5a3 Mon Sep 17 00:00:00 2001 From: Lukas Gasselsberger | alu-one Date: Fri, 8 Apr 2022 06:20:21 +0200 Subject: [PATCH] Add the `HaveSingleElement`, `HaveElement` to `XElementAssertions` as well --- .../Xml/XDocumentAssertions.cs | 4 +- .../Xml/XElementAssertions.cs | 120 ++++++++++++++ .../Xml/XElementAssertionSpecs.cs | 153 ++++++++++++++++++ 3 files changed, 275 insertions(+), 2 deletions(-) diff --git a/Src/FluentAssertions/Xml/XDocumentAssertions.cs b/Src/FluentAssertions/Xml/XDocumentAssertions.cs index ed07c58552..41e46f3d35 100644 --- a/Src/FluentAssertions/Xml/XDocumentAssertions.cs +++ b/Src/FluentAssertions/Xml/XDocumentAssertions.cs @@ -266,7 +266,7 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected } /// - /// Asserts that the element of the current has a _single_ + /// Asserts that the element of the current has a single /// child element with the specified name. /// /// @@ -285,7 +285,7 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected } /// - /// Asserts that the element of the current has a _single_ + /// Asserts that the element of the current has a single /// child element with the specified name. /// /// diff --git a/Src/FluentAssertions/Xml/XElementAssertions.cs b/Src/FluentAssertions/Xml/XElementAssertions.cs index c0f2a0d4cb..3772a1f30d 100644 --- a/Src/FluentAssertions/Xml/XElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XElementAssertions.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Xml; using System.Xml.Linq; using FluentAssertions.Common; @@ -288,6 +290,124 @@ public AndConstraint HaveValue(string expected, string becau return new AndWhichConstraint(this, xElement); } + /// + /// Asserts that the of the current has a single + /// child element with the specified name. + /// + /// + /// The full name of the expected child element of the current element's . + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndWhichConstraint HaveSingleElement(XName expected, string because = "", params object[] becauseArgs) + { + return HaveElement(expected, Exactly.Once(), because, becauseArgs); + } + + /// + /// Asserts that the of the current has a single + /// child element with the specified name. + /// + /// + /// The full name of the expected child element of the current element's . + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndWhichConstraint HaveSingleElement(string expected, string because = "", params object[] becauseArgs) + { + return HaveElement(XNamespace.None + expected, Exactly.Once(), because, becauseArgs); + } + + /// + /// Asserts that the of the current has the specified occurrence of + /// child elements with the specified name. + /// + /// + /// The full name of the expected child element of the current element's . + /// + /// + /// A constraint specifying the number of times the specified elements should appear. + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndWhichConstraint HaveElement(XName expected, + OccurrenceConstraint occurrenceConstraint, string because = "", + params object[] becauseArgs) + { + Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Cannot assert the count if the element itself is ."); + + Guard.ThrowIfArgumentIsNull(expected, nameof(expected), + "Cannot assert the element has an element count if the element name is ."); + + bool success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith( + "Expected {context:subject} to have an element with count of {0}{reason}, but it has no elements.", + expected.ToString()); + + IEnumerable xElements = Enumerable.Empty(); + + if (success) + { + xElements = Subject.Elements(expected); + int actualCount = xElements.Count(); + + Execute.Assertion + .ForConstraint(occurrenceConstraint, actualCount) + .BecauseOf(because, becauseArgs) + .FailWith( + "Expected {context:subject} to have {0} child element(s) {1}{reason}, but found {2}.", + occurrenceConstraint.ExpectedCount, expected.ToString(), actualCount); + } + + return new AndWhichConstraint(this, xElements); + } + + /// + /// Asserts that the of the current has a direct + /// child element with the specified name. + /// + /// + /// The name of the expected child element of the current element's . + /// + /// + /// A constraint specifying the number of times the specified elements should appear. + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndWhichConstraint HaveElement(string expected, + OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expected, nameof(expected), + "Cannot assert the element has an element if the expected name is ."); + + return HaveElement(XNamespace.None + expected, occurrenceConstraint, because, becauseArgs); + } + /// /// Returns the type of the subject the assertion applies on. /// diff --git a/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs index 3f3364b47a..df42cf9cb4 100644 --- a/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs @@ -1228,5 +1228,158 @@ public void When_asserting_element_has_a_child_element_with_an_empty_name_it_sho } #endregion + + #region HaveElement (with occurrence) + + [Fact] + public void Element_has_two_child_elements_and_it_expected_does_it_succeeds() + { + // Arrange + var document = XElement.Parse( + @" + + + "); + + // Act / Assert + document.Should().HaveElement("child", Exactly.Twice()); + } + + [Fact] + public void Element_has_two_child_elements_and_three_expected_it_fails() + { + // Arrange + var document = XElement.Parse( + @" + + + + "); + + // Act + Action act = () => document.Should().HaveElement("child", Exactly.Twice()); + + // Assert + act.Should().Throw().WithMessage( + "Expected element to have 2 child element(s) \"child\", but found 3."); + } + + [Fact] + public void Chaining_which_after_asserting_and_the_element_has_more_than_two_child_elements_it_fails() + { + // Arrange + var document = XElement.Parse( + @" + + + + "); + + // Act + Action act = () => document.Should().HaveElement("child", AtLeast.Twice()) + .Which.Should().NotBeNull(); + + // Assert + act.Should().Throw().WithMessage( + "More than one object found. FluentAssertions cannot determine which object is meant*"); + } + + [Fact] + public void Null_element_is_expected_to_have_an_element_count_it_should_fail() + { + // Arrange + XElement xElement = null; + + // Act + Action act = () => xElement.Should().HaveElement("child", AtLeast.Once()); + + // Assert + act.Should().Throw().WithMessage( + "Cannot assert the count if the element itself is ."); + } + + #endregion + + #region HaveSingleElement + + [Fact] + public void A_single_expected_child_element_with_the_specified_name_should_be_accepted() + { + // Arrange + var document = XElement.Parse( + @" + + "); + + // Act / Assert + document.Should().HaveSingleElement("child"); + } + + [Fact] + public void Chaining_which_after_asserting_on_singularity_passes() + { + // Arrange + var document = XElement.Parse( + @" + + "); + + // Act / Assert + document.Should().HaveSingleElement("child").Which.Should().HaveAttribute("foo", "bar"); + } + + [Fact] + public void Chaining_which_after_asserting_on_singularity_fails_if_element_is_not_single() + { + // Arrange + var document = XElement.Parse( + @" + + + "); + + // Act + Action act = () => document.Should().HaveSingleElement("child") + .Which.Should().HaveAttribute("foo", "bar"); + + // Assert + act.Should().Throw() + .WithMessage("Expected element to have 1 child element(s) \"child\", but found 2."); + } + + [Fact] + public void Multiple_child_elements_with_the_specified_name_should_fail_the_test() + { + // Arrange + var document = XElement.Parse( + @" + + + "); + + // Act + Action act = () => document.Should().HaveSingleElement("child", + "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected element to have 1 child element(s) \"child\"*failure message*, but found 2."); + } + + [Fact] + public void Cannot_ensure_a_single_element_if_the_document_is_null() + { + // Arrange + XElement xElement = null; + + // Act + Action act = () => xElement.Should().HaveSingleElement("child"); + + // Assert + act.Should().Throw().WithMessage( + "Cannot assert the count if the element itself is ."); + } + + #endregion } }