Skip to content

Commit

Permalink
Add the HaveSingleElement, HaveElement to XElementAssertions as…
Browse files Browse the repository at this point in the history
… well
  • Loading branch information
ITaluone committed Apr 8, 2022
1 parent 43dabcf commit 223aaec
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 3 deletions.
4 changes: 2 additions & 2 deletions Src/FluentAssertions/Xml/XDocumentAssertions.cs
Expand Up @@ -266,7 +266,7 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
}

/// <summary>
/// Asserts that the <see cref="XDocument.Root"/> element of the current <see cref="XDocument"/> has a _single_
/// Asserts that the <see cref="XDocument.Root"/> element of the current <see cref="XDocument"/> has a <i>single</i>
/// child element with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
Expand All @@ -285,7 +285,7 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
}

/// <summary>
/// Asserts that the <see cref="XDocument.Root"/> element of the current <see cref="XDocument"/> has a _single_
/// Asserts that the <see cref="XDocument.Root"/> element of the current <see cref="XDocument"/> has a <i>single</i>
/// child element with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
Expand Down
115 changes: 115 additions & 0 deletions 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;
Expand Down Expand Up @@ -288,6 +290,119 @@ public AndConstraint<XElementAssertions> HaveValue(string expected, string becau
return new AndWhichConstraint<XElementAssertions, XElement>(this, xElement);
}

/// <summary>
/// Asserts that the <see cref="XElement"/> of the current <see cref="XElement"/> has a <i>single</i>
/// child element with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
/// The full name <see cref="XName"/> of the expected child element of the current element's <see cref="XElement"/>.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndWhichConstraint<XElementAssertions, XElement> HaveSingleElement(XName expected, string because = "", params object[] becauseArgs)
{
return HaveElement(expected, Exactly.Once(), because, becauseArgs);
}

/// <summary>
/// Asserts that the <see cref="XElement"/> of the current <see cref="XElement"/> has a <i>single</i>
/// child element with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
/// The full name of the expected child element of the current element's <see cref="XElement"/>.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndWhichConstraint<XElementAssertions, XElement> HaveSingleElement(string expected, string because = "", params object[] becauseArgs)
{
return HaveElement(XNamespace.None + expected, Exactly.Once(), because, becauseArgs);
}

/// <summary>
/// Asserts that the <see cref="XElement"/> of the current <see cref="XElement"/> has the specified occurrence of
/// child elements with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
/// The full name <see cref="XName"/> of the expected child element of the current element's <see cref="XElement"/>.
/// </param>
/// <param name="occurrenceConstraint">
/// A constraint specifying the number of times the specified elements should appear.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndWhichConstraint<XElementAssertions, XElement> HaveElement(XName expected,
OccurrenceConstraint occurrenceConstraint, string because = "",
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(expected, nameof(expected),
"Cannot assert the element has an element count if the element name is <null>.");

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 the element itself is <null>.",
expected.ToString());

IEnumerable<XElement> xElements = Enumerable.Empty<XElement>();

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<XElementAssertions, XElement>(this, xElements);
}

/// <summary>
/// Asserts that the <see cref="XElement"/> of the current <see cref="XElement"/> has a direct
/// child element with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
/// The name of the expected child element of the current element's <see cref="XElement"/>.
/// </param>
/// <param name="occurrenceConstraint">
/// A constraint specifying the number of times the specified elements should appear.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndWhichConstraint<XElementAssertions, XElement> 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 <null>.");

return HaveElement(XNamespace.None + expected, occurrenceConstraint, because, becauseArgs);
}

/// <summary>
/// Returns the type of the subject the assertion applies on.
/// </summary>
Expand Down
15 changes: 14 additions & 1 deletion Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs
Expand Up @@ -1245,6 +1245,19 @@ public void A_single_expected_child_element_with_the_specified_name_should_be_ac
document.Should().HaveSingleElement("child");
}

[Fact]
public void A_single_expected_child_element_with_the_specified_xml_name_should_be_accepted()
{
// Arrange
var document = XDocument.Parse(
@"<parent>
<child />
</parent>");

// Act / Assert
document.Should().HaveSingleElement(XNamespace.None + "child");
}

[Fact]
public void Chaining_which_after_asserting_on_singularity_passes()
{
Expand All @@ -1257,7 +1270,7 @@ public void Chaining_which_after_asserting_on_singularity_passes()
// 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()
{
Expand Down
166 changes: 166 additions & 0 deletions Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs
Expand Up @@ -1228,5 +1228,171 @@ 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 element = XElement.Parse(
@"<parent>
<child />
<child />
</parent>");

// Act / Assert
element.Should().HaveElement("child", Exactly.Twice());
}

[Fact]
public void Element_has_two_child_elements_and_three_expected_it_fails()
{
// Arrange
var element = XElement.Parse(
@"<parent>
<child />
<child />
<child />
</parent>");

// Act
Action act = () => element.Should().HaveElement("child", Exactly.Twice());

// Assert
act.Should().Throw<XunitException>().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 element = XElement.Parse(
@"<parent>
<child />
<child />
<child />
</parent>");

// Act
Action act = () => element.Should().HaveElement("child", AtLeast.Twice())
.Which.Should().NotBeNull();

// Assert
act.Should().Throw<XunitException>().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<XunitException>().WithMessage(
"Expected* to have an element with count of *, but the element itself is <null>.");
}

#endregion

#region HaveSingleElement

[Fact]
public void A_single_expected_child_element_with_the_specified_name_should_be_accepted()
{
// Arrange
var element = XElement.Parse(
@"<parent>
<child />
</parent>");

// Act / Assert
element.Should().HaveSingleElement("child");
}

[Fact]
public void A_single_expected_child_element_with_the_specified_xml_name_should_be_accepted()
{
// Arrange
var element = XElement.Parse(
@"<parent>
<child />
</parent>");

// Act / Assert
element.Should().HaveSingleElement(XNamespace.None + "child");
}

[Fact]
public void Chaining_which_after_asserting_on_singularity_passes()
{
// Arrange
var element = XElement.Parse(
@"<parent>
<child foo=""bar""/>
</parent>");

// Act / Assert
element.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 element = XElement.Parse(
@"<parent>
<child foo=""bar""/>
<child foo=""bar""/>
</parent>");

// Act
Action act = () => element.Should().HaveSingleElement("child")
.Which.Should().HaveAttribute("foo", "bar");

// Assert
act.Should().Throw<XunitException>()
.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 element = XElement.Parse(
@"<parent>
<child />
<child />
</parent>");

// Act
Action act = () => element.Should().HaveSingleElement("child",
"because we want to test the failure {0}", "message");

// Assert
act.Should().Throw<XunitException>().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<XunitException>().WithMessage(
"Expected* to have an element with count of *, but the element itself is <null>.");
}

#endregion
}
}

0 comments on commit 223aaec

Please sign in to comment.