Skip to content

Add Xunit.Assert extension methods for comparison and null/empty assertions - #86

Draft
Burgyn with Copilot wants to merge 4 commits into
masterfrom
copilot/add-xunit-assert-extensions
Draft

Add Xunit.Assert extension methods for comparison and null/empty assertions#86
Burgyn with Copilot wants to merge 4 commits into
masterfrom
copilot/add-xunit-assert-extensions

Conversation

Copilot AI commented Mar 15, 2026

Copy link
Copy Markdown
Contributor
  • Initial implementation of XunitAssertExtensions with tests and docs
  • Merge master branch (argument-order fixes for existing test files)
  • Upgrade projects to .NET 10 with C# 14 extension types
  • Fix argument order: Assert.GreaterThan(limit, value) following xUnit convention
  • Migrate assertions to C# 14 extension(Assert) syntax in Xunit namespace
  • Update unit tests, demo, and docs
  • Fix .NET 10 breaking change in HeadersResolverShould
  • Fix CI build failure: CA2264 in StepsCollection.cs — all 6 ArgumentNullException.ThrowIfNull(nameof(x)) calls were passing the string name instead of the variable; replaced with ThrowIfNull(x)
Original prompt

This section details on the original issue you should resolve

<issue_title>🚀 [NEW] Xunit.Assert Extensions</issue_title>
<issue_description>## 🫴 Proposal
The main goal of this feature proposal is to add new handy methods which will extend assertion capabilities of Xunit.Assert used by default. Here is a list of methods that came to my mind:

GreaterThan(input, than) // For all numeric type - for number with floating point, add epsilon parameter
GreaterThanOrEqual(input, than) // For all numeric type - for number with floating point, add epsilon parameter
LessThan(input, than) // For all numeric type - for number with floating point, add epsilon parameter
LessThanOrEqual(input, than) // For all numeric type - for number with floating point, add epsilon parameter
NullOrEmpty(input) // For strings and collections
NotNullOrEmpty(input) // For strings and collections

✨ Motivation

By solely using Xunit.Assert some assertions became awkward, for example:

// Xunit.Assert usage
True(myNumber < 20);

// Desired usage
LessThan(myNumber, 20);

In the provided example, True assertion doesn't provide enough information about what have failed - it just says that false should be true, which in this context doesn't help at all. With the new approach end-user will have more possibilities and more comprehensive output will be provided.

🔀 Alternatives

Listed methods comes from small brainstorming, so adding more/different methods is adequate for this issue.

💡 Possible implementation

This should be one of the easier tasks. Adding new class with extension methods over Xunit.Assert class. Don't forget to add variants of methods for all supported data types.

➕ Additional context

The initial idea was to use FluentAssertions as default assertion language, but after critical changes in its licensing, we have decided to choose Xunit.Assert instead. We are aware, that fluent assertions would contribute to better user experience than currently chosen solution, but its pricing is an obstacle which can not be overpassed currently in this project.

More info: fluentassertions/fluentassertions#2943</issue_description>

<agent_instructions>Try to implement this feature</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@matejicko Hi @StrikzZ

First of all, thank you for willingness to help this project!

I have checked your work and it seems that you are on the right path. I have just few minor comments:

  • Not sure whether actual and expected is the best wording for this case, I would maybe use value and limit or maybe actual and threshold
  • Unit tests naming conventions work in the way, that class name + method name should compose meaningful sentence. For example "Xunit Assert Extensions Should | Evaluate Greater Than Assertion Correctly". The names of methods are written in PascalCase, without underscores (_).
  • For parametrized method GreaterThan<T> it can be said in the comment, that it is universal for all types implementing IComparable<T> interface (not exclusively for those provided in this issue and your comment)

Apart of these details, it looks great. I appreciate that you also included unit tests automatically. To make your pull request even better, please consider putting some example of usage to demo. Now, I am also thinking about adding new article to Appendix section in the documentation with name Xunit.Assert Extensions where would be a list of extensions provided by TeaPie.

If you need any help, let me know!

Thanks!</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…h tests, docs, and demo

Co-authored-by: Burgyn <5930822+Burgyn@users.noreply.github.com>
Copilot AI changed the title [WIP] [NEW] Add Xunit.Assert extensions for enhanced assertions Add Xunit.Assert extension methods for comparison and null/empty assertions Mar 15, 2026
Copilot AI requested a review from Burgyn March 15, 2026 12:50
@satano

satano commented Mar 16, 2026

Copy link
Copy Markdown
Member

We are using arguments od Assert class incorrectly. I created an issue #87 for it. So I would recomment even these extension methods to follow the Assert class practice. First argument is expected, second is actual. For example:

GreaterThan(value, limit[, epsilon])

should be

GreaterThan(limit, value[, epsilon])

I recommend rewriting original issue #69 and description of this PR to address this.

@Burgyn Burgyn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upgrade project to .NET 10 / C# 10 and use new type of extension methods https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14. Then we can use this extension as we use standard XUnit assertion Assert.GreaterThan (...)

namespace Xunit;

public static class AssertExtensions
{
    extension(Assert)
    {
        public static void GreaterThan<T>(T limit, T value) where T : IComparable<T>
        {
            if (value.CompareTo(limit) <= 0)
            {
                Fail($"Expected value to be greater than {limit}, but found {value}.");
            }
        }
    }
}

@Burgyn

Burgyn commented Mar 16, 2026

Copy link
Copy Markdown
Member

@copilot merge master to this branch and fix comments.

…er (limit first, value second)

Co-authored-by: Burgyn <5930822+Burgyn@users.noreply.github.com>

Copilot AI commented Mar 16, 2026

Copy link
Copy Markdown
Contributor Author

We are using arguments od Assert class incorrectly. I created an issue #87 for it. So I would recomment even these extension methods to follow the Assert class practice. First argument is _expecte...

Fixed in 9e1f92b. All comparison methods now follow the xUnit convention: first argument is limit (expected threshold), second is value (actual). For example: Assert.GreaterThan(limit, value), Assert.LessThan(limit, value), etc.

Upgrade project to .NET 10 / C# 10 and use new type of extension methods https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14. Then we can use this extension as we use standard XUnit a...

Done in 9e1f92b. Upgraded both src/Directory.Build.props and tests/Directory.Build.props to net10.0 with LangVersion=preview. The new comparison and null/empty assertion methods are now implemented in src/TeaPie/Testing/AssertExtensions.cs using C# 14 extension(Assert) syntax in the Xunit namespace, so they're callable as Assert.GreaterThan(limit, value), Assert.NullOrEmpty(value), etc.

@copilot merge master to this branch and fix comments.

Done in 9e1f92b. Merged master (which had argument-order fixes for existing tests), upgraded to .NET 10 / C# 14, and implemented the extension type approach and argument order fix per the other comments.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/TeaPie.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/TeaPie.Tests.deps.json /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/testhost.dll --port 42565 --endpoint 127.0.0.1:042565 --role client --parentprocessid 5468 --telemetryoptedin false (dns block)
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/TeaPie.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/TeaPie.Tests.deps.json /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/testhost.dll --port 38247 --endpoint 127.0.0.1:038247 --role client --parentprocessid 5978 --telemetryoptedin false (dns block)
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/TeaPie.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/TeaPie.Tests.deps.json /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Debug/net10.0/testhost.dll --port 39275 --endpoint 127.0.0.1:039275 --role client --parentprocessid 6657 --telemetryoptedin false (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from Burgyn March 16, 2026 20:44
@Burgyn

Burgyn commented Mar 17, 2026

Copy link
Copy Markdown
Member
Run dotnet build --configuration Release --no-restore -warnaserror
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(14,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(15,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(30,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(38,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(39,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(58,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
  TeaPie -> /home/runner/work/TeaPie/TeaPie/src/TeaPie/bin/Release/net10.0/TeaPie.dll
  Successfully created package '/home/runner/work/TeaPie/TeaPie/src/TeaPie/bin/Release/TeaPie.1.5.2.nupkg'.
  TeaPie.DotnetTool -> /home/runner/work/TeaPie/TeaPie/src/TeaPie.DotnetTool/bin/Release/net10.0/TeaPie.DotnetTool.dll
  Successfully created package '/home/runner/work/TeaPie/TeaPie/src/TeaPie.DotnetTool/bin/Release/TeaPie.Tool.1.5.2.nupkg'.
  TeaPie.Tests -> /home/runner/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Release/net10.0/TeaPie.Tests.dll

Build FAILED.

Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(14,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(15,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(30,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(38,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(39,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(58,9): error CA2264: Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) [/home/runner/work/TeaPie/TeaPie/src/TeaPie/TeaPie.csproj]
    0 Warning(s)
    6 Error(s)

Time Elapsed 00:00:18.08
Error: Process completed with exit code 1.

CI Build failed. Maybe .net version in github action?

@copilot fix it.

…l (not nameof strings)

Co-authored-by: Burgyn <5930822+Burgyn@users.noreply.github.com>

Copilot AI commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author
Run dotnet build --configuration Release --no-restore -warnaserror
Error: /home/runner/work/TeaPie/TeaPie/src/TeaPie/Pipelines/StepsCollection.cs(14,9): error CA2264: Calling 'ArgumentNullEx...

Fixed in b9a2b61. The root cause was a pre-existing bug in StepsCollection.cs: all 6 ArgumentNullException.ThrowIfNull calls were passing nameof(x) (a non-nullable string constant) instead of the actual variable x. .NET 10's CA2264 rule now catches this and the --warnaserror flag turned it into a build error. Changed all occurrences to pass the variable directly (e.g. ThrowIfNull(predecessor) instead of ThrowIfNull(nameof(predecessor))).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Release/net10.0/TeaPie.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Release/net10.0/TeaPie.Tests.deps.json /home/REDACTED/work/TeaPie/TeaPie/tests/TeaPie.Tests/bin/Release/net10.0/testhost.dll --port 41569 --endpoint 127.0.0.1:041569 --role client --parentprocessid 4075 --telemetryoptedin false (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🚀 [NEW] Xunit.Assert Extensions

3 participants