Skip to content

Commit

Permalink
Add overload to HaveElement() to be able to assert on occurrences f…
Browse files Browse the repository at this point in the history
…or `XDocument` and `XElement` (#1880)
  • Loading branch information
ITaluone committed Apr 23, 2022
1 parent 9cf8638 commit 68f41b9
Show file tree
Hide file tree
Showing 12 changed files with 510 additions and 6 deletions.
99 changes: 93 additions & 6 deletions Src/FluentAssertions/Xml/XDocumentAssertions.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

Expand Down Expand Up @@ -132,7 +134,7 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(expected, nameof(expected),
"Cannot assert the document has a root element if the expected name is <null>*");
"Cannot assert the document has a root element if the expected name is <null>.");

return HaveRoot(XNamespace.None + expected, because, becauseArgs);
}
Expand All @@ -157,7 +159,7 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
}

Guard.ThrowIfArgumentIsNull(expected, nameof(expected),
"Cannot assert the document has a root element if the expected name is <null>*");
"Cannot assert the document has a root element if the expected name is <null>.");

XElement root = Subject.Root;

Expand All @@ -176,7 +178,7 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
/// child element with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
/// The name of the expected child element of the current document's Root <see cref="XDocument.Root"/> element.
/// The name of the expected child element of the current document's <see cref="XDocument.Root"/> element.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
Expand All @@ -189,17 +191,43 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(expected, nameof(expected),
"Cannot assert the document has an element if the expected name is <null>*");
"Cannot assert the document has an element if the expected name is <null>.");

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

/// <summary>
/// Asserts that the <see cref="XDocument.Root"/> element of the current <see cref="XDocument"/> has the specified occurrence of
/// child elements with the specified <paramref name="expected"/> name.
/// </summary>
/// <param name="expected">
/// The name of the expected child element of the current document's <see cref="XDocument.Root"/> element.
/// </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<XDocumentAssertions, IEnumerable<XElement>> HaveElement(string expected,
OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(expected, nameof(expected),
"Cannot assert the document has an element if the expected name is <null>.");

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

/// <summary>
/// Asserts that the <see cref="XDocument.Root"/> element of the current <see cref="XDocument"/> has a direct
/// 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 document's Root <see cref="XDocument.Root"/> element.
/// The full name <see cref="XName"/> of the expected child element of the current document's <see cref="XDocument.Root"/> element.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
Expand All @@ -217,7 +245,7 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
}

Guard.ThrowIfArgumentIsNull(expected, nameof(expected),
"Cannot assert the document has an element if the expected name is <null>*");
"Cannot assert the document has an element if the expected name is <null>.");

Execute.Assertion
.ForCondition(Subject.Root is not null)
Expand All @@ -237,6 +265,65 @@ public AndConstraint<XDocumentAssertions> NotBeEquivalentTo(XDocument unexpected
return new AndWhichConstraint<XDocumentAssertions, XElement>(this, xElement);
}

/// <summary>
/// Asserts that the <see cref="XDocument.Root"/> element of the current <see cref="XDocument"/> 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 document's <see cref="XDocument.Root"/> element.
/// </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<XDocumentAssertions, IEnumerable<XElement>> HaveElement(XName expected,
OccurrenceConstraint occurrenceConstraint, string because = "",
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(expected, nameof(expected),
"Cannot assert the document has an element count if the element name is <null>.");

bool success = Execute.Assertion
.ForCondition(Subject is not null)
.BecauseOf(because, becauseArgs)
.FailWith("Cannot assert the count if the document itself is <null>.");

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

if (success)
{
var root = Subject.Root;
success = Execute.Assertion
.ForCondition(root is not null)
.BecauseOf(because, becauseArgs)
.FailWith(
"Expected {context:subject} to have root element containing a child {0}{reason}, but it has no root element.",
expected.ToString());

if (success)
{
xElements = root.Elements(expected);
int actual = xElements.Count();

Execute.Assertion
.ForConstraint(occurrenceConstraint, actual)
.BecauseOf(because, becauseArgs)
.FailWith(
$"Expected {{context:subject}} to have a root element containing a child {{0}} " +
$"{{expectedOccurrence}}{{reason}}, but found it {actual.Times()}.",
expected.ToString());
}
}

return new AndWhichConstraint<XDocumentAssertions, IEnumerable<XElement>>(this, xElements);
}

/// <summary>
/// Returns the type of the subject the assertion applies on.
/// </summary>
Expand Down
78 changes: 78 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,82 @@ 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 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, IEnumerable<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 actual = xElements.Count();

Execute.Assertion
.ForConstraint(occurrenceConstraint, actual)
.BecauseOf(because, becauseArgs)
.FailWith(
$"Expected {{context:subject}} to have an element {{0}} {{expectedOccurrence}}" +
$"{{reason}}, but found it {actual.Times()}.",
expected.ToString());
}

return new AndWhichConstraint<XElementAssertions, IEnumerable<XElement>>(this, xElements);
}

/// <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 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, IEnumerable<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
Expand Up @@ -2685,6 +2685,8 @@ namespace FluentAssertions.Xml
public FluentAssertions.AndConstraint<FluentAssertions.Xml.XDocumentAssertions> BeEquivalentTo(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XDocumentAssertions, System.Xml.Linq.XElement> HaveElement(string expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XDocumentAssertions, System.Xml.Linq.XElement> HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XDocumentAssertions, System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XDocumentAssertions, System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XDocumentAssertions, System.Xml.Linq.XElement> HaveRoot(string expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XDocumentAssertions, System.Xml.Linq.XElement> HaveRoot(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<FluentAssertions.Xml.XDocumentAssertions> NotBe(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { }
Expand All @@ -2700,6 +2702,8 @@ namespace FluentAssertions.Xml
public FluentAssertions.AndConstraint<FluentAssertions.Xml.XElementAssertions> HaveAttribute(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XElementAssertions, System.Xml.Linq.XElement> HaveElement(string expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XElementAssertions, System.Xml.Linq.XElement> HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XElementAssertions, System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndWhichConstraint<FluentAssertions.Xml.XElementAssertions, System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<FluentAssertions.Xml.XElementAssertions> HaveValue(string expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<FluentAssertions.Xml.XElementAssertions> NotBe(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<FluentAssertions.Xml.XElementAssertions> NotBeEquivalentTo(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { }
Expand Down

0 comments on commit 68f41b9

Please sign in to comment.