Skip to content

Commit

Permalink
Add support for ArgumentException (#1730)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsking authored Jan 31, 2025
1 parent 8a455c4 commit 7cca570
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Diagnostics.CodeAnalysis;
using TUnit.Assertions.AssertConditions.Throws;

namespace TUnit.Assertions.Tests.Assertions.Delegates;

public partial class Throws
{
public class WithParameterName
{
[Test]
public async Task Fails_For_Different_Parameter_Name()
{
var paramName1 = "foo";
var paramName2 = "bar";
var expectedMessage = """
Expected action to throw an ArgumentException which param name equals "bar"
but it differs at index 0:
"foo"
"bar"
at Assert.That(action).ThrowsExactly<ArgumentException>().WithParameterName(paramName2)
""";
ArgumentException exception = new(string.Empty, paramName1);
Action action = () => throw exception;

var sut = async ()
=> await Assert.That(action).ThrowsExactly<ArgumentException>().WithParameterName(paramName2);

await Assert.That(sut).ThrowsException()
.WithMessage(expectedMessage);
}

[Test]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task Returns_Exception_When_Awaited()
{
var matchingParamName = "foo";
ArgumentException exception = new(string.Empty, matchingParamName);
Action action = () => throw exception;

var result = await Assert.That(action).Throws<ArgumentException>().WithParameterName(matchingParamName);

await Assert.That(result).IsSameReferenceAs(exception);
}

[Test]
public async Task Succeed_For_Matching_Parameter_Name()
{
var matchingParamName = "foo";
ArgumentException exception = new(string.Empty, matchingParamName);
Action action = () => throw exception;

var sut = async ()
=> await Assert.That(action).Throws<ArgumentException>().WithParameterName(matchingParamName);

await Assert.That(sut).ThrowsNothing();
}
}
}
4 changes: 4 additions & 0 deletions TUnit.Assertions/Assertions/Throws/ThrowsException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public AndConvertedTypeAssertionBuilder<TException> And

public DelegateOr<object?> Or => _delegateAssertionBuilder.Or;

internal void RegisterAssertion(BaseAssertCondition<TActual> condition, string?[] argumentExpressions, [CallerMemberName] string? caller = null) => _source.RegisterAssertion(condition, argumentExpressions, caller);

internal void RegisterAssertion(Func<Func<Exception?, Exception?>, BaseAssertCondition<TActual>> conditionFunc, string?[] argumentExpressions, [CallerMemberName] string? caller = null) => _source.RegisterAssertion(conditionFunc(_selector), argumentExpressions, caller);

private async ValueTask<AssertionData> AssertionDataTask()
{
var value = await this;
Expand Down
11 changes: 10 additions & 1 deletion TUnit.Assertions/Assertions/Throws/ThrowsExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using TUnit.Assertions.AssertConditions.Interfaces;
using System.Runtime.CompilerServices;
using TUnit.Assertions.AssertConditions.Exceptions;
using TUnit.Assertions.AssertConditions.Interfaces;
using TUnit.Assertions.AssertionBuilders;
using TUnit.Assertions.Extensions;

Expand Down Expand Up @@ -38,4 +40,11 @@ public static class ThrowsExtensions
delegateSource.RegisterAssertion(new ThrowsNothingAssertCondition<object?>(), []),
assertionData => assertionData.Result);
}

public static ThrowsException<TActual, TException> WithParameterName<TActual, TException>(this ThrowsException<TActual, TException> throwsException, string expected, [CallerArgumentExpression(nameof(expected))] string? doNotPopulateThisValue = null)
where TException : ArgumentException
{
throwsException.RegisterAssertion((selector) => new ThrowsWithParamNameAssertCondition<TActual, TException>(expected, StringComparison.Ordinal, ex => selector(ex) as ArgumentException), [doNotPopulateThisValue]);
return throwsException;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using TUnit.Assertions.Extensions;
using TUnit.Assertions.Helpers;

namespace TUnit.Assertions.AssertConditions.Throws;

public class ThrowsWithParamNameAssertCondition<TActual, TException>(
string expectedParamName,
StringComparison stringComparison,
Func<Exception?, ArgumentException?> exceptionSelector)
: DelegateAssertCondition<TActual, ArgumentException>()
where TException : ArgumentException
{
protected override string GetExpectation()
=> $"to throw {typeof(TException).Name.PrependAOrAn()} which param name equals \"{expectedParamName.TruncateWithEllipsis(100)}\"";

protected override Task<AssertionResult> GetResult(TActual? actualValue, Exception? exception)
{
var actualException = exceptionSelector(exception);

return AssertionResult
.FailIf(actualException is null,
"the exception is null")
.OrFailIf(!string.Equals(actualException!.ParamName, expectedParamName, stringComparison),
$"{new StringDifference(actualException!.ParamName, expectedParamName)
.ToString("it differs at index")}");
}
}

0 comments on commit 7cca570

Please sign in to comment.