Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 57 additions & 72 deletions TUnit.Assertions/Assert.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,62 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using TUnit.Assertions.AssertConditions.Throws;
using TUnit.Assertions.AssertionBuilders;
using TUnit.Assertions.Extensions;
using TUnit.Assertions.Helpers;
using TUnit.Assertions.Wrappers;

namespace TUnit.Assertions;

[SuppressMessage("Usage", "TUnitAssertions0002:Assert statements must be awaited")]
public static class Assert
{
public static ValueAssertionBuilder<TActual> That<TActual>(TActual value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new ValueAssertionBuilder<TActual>(value, doNotPopulateThisValue);
}

public static ValueAssertionBuilder<IEnumerable<object>> That(IEnumerable enumerable, [CallerArgumentExpression(nameof(enumerable))] string? doNotPopulateThisValue = null)
{
return new ValueAssertionBuilder<IEnumerable<object>>(new UnTypedEnumerableWrapper(enumerable), doNotPopulateThisValue);
}

public static DelegateAssertionBuilder That(Action value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new DelegateAssertionBuilder(value, doNotPopulateThisValue);
}

public static ValueDelegateAssertionBuilder<TActual> That<TActual>(Func<TActual> value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new ValueDelegateAssertionBuilder<TActual>(value, doNotPopulateThisValue);
}

public static AsyncDelegateAssertionBuilder That(Func<Task> value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new AsyncDelegateAssertionBuilder(value, doNotPopulateThisValue);
}

public static AsyncValueDelegateAssertionBuilder<TActual> That<TActual>(Func<Task<TActual>> value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new AsyncValueDelegateAssertionBuilder<TActual>(value, doNotPopulateThisValue);
}

public static AsyncDelegateAssertionBuilder That(Task value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new AsyncDelegateAssertionBuilder(async () => await value, doNotPopulateThisValue);
}

public static AsyncValueDelegateAssertionBuilder<TActual> That<TActual>(Task<TActual> value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new AsyncValueDelegateAssertionBuilder<TActual>(async () => await value, doNotPopulateThisValue);
}

public static AsyncDelegateAssertionBuilder That(ValueTask value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new AsyncDelegateAssertionBuilder(async () => await value, doNotPopulateThisValue);
}

public static AsyncValueDelegateAssertionBuilder<TActual> That<TActual>(ValueTask<TActual> value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null)
{
return new AsyncValueDelegateAssertionBuilder<TActual>(async () => await value, doNotPopulateThisValue);
Expand All @@ -65,87 +67,70 @@ public static IDisposable Multiple()
return new AssertionScope();
}

public static Task<Exception> ThrowsAsync(Func<Task> @delegate,
public static ThrowsException<object?, Exception> ThrowsAsync(Func<Task> @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
=> ThrowsAsync<Exception>(@delegate, doNotPopulateThisValue);

public static Task<Exception> ThrowsAsync(Task @delegate,
public static ThrowsException<object?, Exception> ThrowsAsync(Task @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
=> ThrowsAsync(async () => await @delegate, doNotPopulateThisValue);

public static Task<Exception> ThrowsAsync(ValueTask @delegate,
public static ThrowsException<object?, Exception> ThrowsAsync(ValueTask @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
=> ThrowsAsync(async () => await @delegate, doNotPopulateThisValue);

public static Task<TException> ThrowsAsync<TException>(Task @delegate,
public static ThrowsException<object?, TException> ThrowsAsync<TException>(Task @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception
=> ThrowsAsync<TException>(async () => await @delegate, doNotPopulateThisValue);

public static Task<TException> ThrowsAsync<TException>(ValueTask @delegate,
public static ThrowsException<object?, TException> ThrowsAsync<TException>(ValueTask @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception
=> ThrowsAsync<TException>(async () => await @delegate, doNotPopulateThisValue);

public static async Task<TException> ThrowsAsync<TException>(Func<Task> @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception
{
return (TException)await ThrowsAsync(typeof(TException), @delegate, doNotPopulateThisValue);
}

public static Task<TException> ThrowsAsync<TException>(string parameterName,
Task @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException
=> ThrowsAsync<TException>(parameterName, async () => await @delegate, doNotPopulateThisValue);

public static Task<TException> ThrowsAsync<TException>(string parameterName,
ValueTask @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException
=> ThrowsAsync<TException>(parameterName, async () => await @delegate, doNotPopulateThisValue);

public static async Task<TException> ThrowsAsync<TException>(string parameterName,
Func<Task> @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException

public static ThrowsException<object?, TException> ThrowsAsync<TException>(Func<Task> @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception
{
var ex = (TException)await ThrowsAsync(typeof(TException), @delegate, doNotPopulateThisValue);

if (ex.ParamName?.Equals(parameterName, StringComparison.Ordinal) == false)
{
Fail($"Incorrect parameter name {new StringDifference(ex.ParamName, parameterName).ToString("it differs at index")}");
}

return ex;
var throwsException = ThrowsAsync(typeof(TException), @delegate, doNotPopulateThisValue);

return Unsafe.As<ThrowsException<object?, TException>>(throwsException);
}

public static Task<Exception> ThrowsAsync(Type type, Task @delegate,
public static ThrowsException<object?, Exception> ThrowsAsync(Type type, Task @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
=> ThrowsAsync(type, async () => await @delegate, doNotPopulateThisValue);

public static Task<Exception> ThrowsAsync(Type type, ValueTask @delegate,
public static ThrowsException<object?, Exception> ThrowsAsync(Type type, ValueTask @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
=> ThrowsAsync(type, async () => await @delegate, doNotPopulateThisValue);

public static async Task<Exception> ThrowsAsync(Type type, Func<Task> @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
public static ThrowsException<object?, Exception> ThrowsAsync(Type type, Func<Task> @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
{
try
{
await @delegate();
}
catch (Exception e) when (type.IsAssignableFrom(e.GetType()))
{
return e;
}
catch (Exception e)
{
Fail($"Exception is of type {e.GetType().Name} instead of {type.Name} for {doNotPopulateThisValue.GetStringOr("the delegate")}");
}
return That(@delegate, doNotPopulateThisValue).Throws(type);
}

public static ThrowsException<object?, TException> ThrowsAsync<TException>(string parameterName,
Task @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null,
[CallerArgumentExpression(nameof(parameterName))] string? doNotPopulateThisValue2 = null) where TException : ArgumentException
=> ThrowsAsync<TException>(parameterName, async () => await @delegate, doNotPopulateThisValue, doNotPopulateThisValue2);

Fail($"No exception was thrown by {doNotPopulateThisValue.GetStringOr("the delegate")}");
public static ThrowsException<object?, TException> ThrowsAsync<TException>(string parameterName,
ValueTask @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null,
[CallerArgumentExpression(nameof(parameterName))] string? doNotPopulateThisValue2 = null) where TException : ArgumentException
=> ThrowsAsync<TException>(parameterName, async () => await @delegate, doNotPopulateThisValue, doNotPopulateThisValue2);

return null;
public static ThrowsException<object?, TException> ThrowsAsync<TException>(string parameterName,
Func<Task> @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null,
[CallerArgumentExpression(nameof(parameterName))] string? doNotPopulateThisValue2 = null) where TException : ArgumentException
{
return That(@delegate, doNotPopulateThisValue).Throws<TException>().WithParameterName(parameterName, doNotPopulateThisValue2);
}

public static Exception Throws(Action @delegate,
[CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
=> Throws<Exception>(@delegate, doNotPopulateThisValue);

public static Exception Throws(Type type, Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null)
{
try
Expand All @@ -160,17 +145,12 @@ public static Exception Throws(Type type, Action @delegate, [CallerArgumentExpre
{
Fail($"Exception is of type {e.GetType().Name} instead of {type.Name} for {doNotPopulateThisValue.GetStringOr("the delegate")}");
}

Fail($"No exception was thrown by {doNotPopulateThisValue.GetStringOr("the delegate")}");

return null;
}

public static TException Throws<TException>(Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception
{
return (TException)Throws(typeof(TException), @delegate, doNotPopulateThisValue);
}


public static TException Throws<TException>(string parameterName, Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException
{
var ex = Throws<TException>(@delegate, doNotPopulateThisValue);
Expand All @@ -182,6 +162,11 @@ public static TException Throws<TException>(string parameterName, Action @delega

return ex;
}

public static TException Throws<TException>(Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception
{
return (TException) Throws(typeof(TException), @delegate, doNotPopulateThisValue);
}

[DoesNotReturn]
public static void Fail(string reason) => TUnit.Assertions.Fail.Test(reason);
Expand Down
2 changes: 2 additions & 0 deletions TUnit.Assertions/Assertions/Throws/ThrowsException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,6 @@ public ThrowsException<TActual, Exception> WithInnerException()
public DelegateOr<object?> Or => _delegateAssertionBuilder.Or;

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

public static explicit operator Task<TException?>(ThrowsException<TActual, TException> throwsException) => Task.Run(async () => await throwsException);
}
26 changes: 21 additions & 5 deletions TUnit.Assertions/Assertions/Throws/ThrowsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public static class ThrowsExtensions
delegateSource,
e => e);
}

public static ThrowsException<object?, Exception> Throws(this IDelegateSource delegateSource, Type type, [CallerArgumentExpression("type")] string? doNotPopulateThisValue = null)
{
return new ThrowsException<object?, Exception>(
delegateSource.RegisterAssertion(new ThrowsOfTypeAssertCondition<object?, Exception>(), [doNotPopulateThisValue]),
delegateSource,
e => e);
}

public static ThrowsException<object?, TException> ThrowsExactly<TException>(this IDelegateSource delegateSource)
where TException : Exception
Expand All @@ -27,16 +35,24 @@ public static class ThrowsExtensions
e => e);
}

public static InvokableDelegateAssertionBuilder ThrowsWithin(this IDelegateSource delegateSource, TimeSpan timeSpan, [CallerArgumentExpression("timeSpan")] string? doNotPopulateThisValue = null)
public static ThrowsException<object?, Exception> ThrowsWithin(this IDelegateSource delegateSource, TimeSpan timeSpan, [CallerArgumentExpression("timeSpan")] string? doNotPopulateThisValue = null)
{
return delegateSource.RegisterAssertion(new ThrowsWithinAssertCondition<object?, Exception>(timeSpan), [doNotPopulateThisValue]);
return new ThrowsException<object?, Exception>(
delegateSource.RegisterAssertion(new ThrowsWithinAssertCondition<object?, Exception>(timeSpan), [doNotPopulateThisValue]),
delegateSource,
e => e
);
}

public static InvokableDelegateAssertionBuilder ThrowsWithin<TException>(this IDelegateSource delegateSource, TimeSpan timeSpan, [CallerArgumentExpression("timeSpan")] string? doNotPopulateThisValue = null)
public static ThrowsException<object?, TException> ThrowsWithin<TException>(this IDelegateSource delegateSource, TimeSpan timeSpan, [CallerArgumentExpression("timeSpan")] string? doNotPopulateThisValue = null)
where TException : Exception
{
return delegateSource.RegisterAssertion(new ThrowsWithinAssertCondition<object?, TException>(timeSpan), [doNotPopulateThisValue],
$"{nameof(ThrowsWithin)}<{typeof(TException).Name}>");
return new ThrowsException<object?, TException>(
delegateSource.RegisterAssertion(new ThrowsWithinAssertCondition<object?, TException>(timeSpan), [doNotPopulateThisValue],
$"{nameof(ThrowsWithin)}<{typeof(TException).Name}>"),
delegateSource,
e => e
);
}

public static ThrowsException<object?, Exception> ThrowsException(this IDelegateSource delegateSource)
Expand Down
27 changes: 22 additions & 5 deletions TUnit.Assertions/Assertions/Throws/ThrowsOfTypeAssertCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@

namespace TUnit.Assertions.AssertConditions.Throws;

public class ThrowsOfTypeAssertCondition<TActual, TExpectedException> : DelegateAssertCondition<TActual, Exception>
public class ThrowsOfTypeAssertCondition(Type type) : DelegateAssertCondition<object?, Exception>
{
internal protected override string GetExpectation()
=> $"to throw {type.Name.PrependAOrAn()}";

protected override ValueTask<AssertionResult> GetResult(
object? actualValue, Exception? exception,
AssertionMetadata assertionMetadata
)
=> AssertionResult
.FailIf(exception is null, "none was thrown")
.OrFailIf(!type.IsAssignableFrom(exception?.GetType()),
$"{exception?.GetType().Name.PrependAOrAn()} was thrown"
);
}

public class ThrowsOfTypeAssertCondition<TActual, TExpectedException> : DelegateAssertCondition<TActual, TExpectedException>
where TExpectedException : Exception
{
internal protected override string GetExpectation()
=> $"to throw {typeof(TExpectedException).Name.PrependAOrAn()}";
Expand All @@ -12,8 +29,8 @@ protected override ValueTask<AssertionResult> GetResult(
AssertionMetadata assertionMetadata
)
=> AssertionResult
.FailIf(exception is null, "none was thrown")
.OrFailIf(!typeof(TExpectedException).IsAssignableFrom(exception?.GetType()),
$"{exception?.GetType().Name.PrependAOrAn()} was thrown"
);
.FailIf(exception is null, "none was thrown")
.OrFailIf(!typeof(TExpectedException).IsAssignableFrom(exception?.GetType()),
$"{exception?.GetType().Name.PrependAOrAn()} was thrown"
);
}
Loading
Loading