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
28 changes: 14 additions & 14 deletions TUnit.Assertions.Tests/TypeOfTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ public async Task IsTypeOf_CustomStruct_Success()
}

[Test]
public async Task IsTypeOf_NullableInt_WithTwoTypeParameters_Success()
public async Task IsTypeOf_NullableInt_Success()
{
int? nullableInt = 42;

var result = await Assert.That(nullableInt).IsTypeOf<int?, int?>();
var result = await Assert.That(nullableInt).IsTypeOf<int?>();

await Assert.That(result).IsEqualTo(42);
}
Expand Down Expand Up @@ -126,8 +126,8 @@ public async Task IsTypeOf_UnboxedInt_Success()
{
int number = 42;

// Using two type parameters - should work with unboxed value types
var result = await Assert.That(number).IsTypeOf<int, int>();
// Now works with single type parameter - source type is inferred
var result = await Assert.That(number).IsTypeOf<int>();

await Assert.That(result).IsEqualTo(42);
}
Expand All @@ -137,7 +137,7 @@ public async Task IsTypeOf_UnboxedDateTime_Success()
{
var date = new DateTime(2025, 10, 14, 12, 30, 0);

var result = await Assert.That(date).IsTypeOf<DateTime, DateTime>();
var result = await Assert.That(date).IsTypeOf<DateTime>();

await Assert.That(result).IsEqualTo(date);
}
Expand All @@ -147,7 +147,7 @@ public async Task IsTypeOf_UnboxedGuid_Success()
{
var guid = Guid.NewGuid();

var result = await Assert.That(guid).IsTypeOf<Guid, Guid>();
var result = await Assert.That(guid).IsTypeOf<Guid>();

await Assert.That(result).IsEqualTo(guid);
}
Expand All @@ -157,7 +157,7 @@ public async Task IsTypeOf_UnboxedBool_Success()
{
bool value = true;

var result = await Assert.That(value).IsTypeOf<bool, bool>();
var result = await Assert.That(value).IsTypeOf<bool>();

await Assert.That(result).IsTrue();
}
Expand All @@ -167,7 +167,7 @@ public async Task IsTypeOf_UnboxedDouble_Success()
{
double value = 3.14159;

var result = await Assert.That(value).IsTypeOf<double, double>();
var result = await Assert.That(value).IsTypeOf<double>();

await Assert.That(result).IsEqualTo(3.14159);
}
Expand All @@ -177,7 +177,7 @@ public async Task IsTypeOf_UnboxedCustomStruct_Success()
{
var customStruct = new CustomStruct { Value = 99, Name = "Unboxed" };

var result = await Assert.That(customStruct).IsTypeOf<CustomStruct, CustomStruct>();
var result = await Assert.That(customStruct).IsTypeOf<CustomStruct>();

await Assert.That(result.Value).IsEqualTo(99);
await Assert.That(result.Name).IsEqualTo("Unboxed");
Expand Down Expand Up @@ -320,8 +320,8 @@ public async Task IsTypeOf_BaseType_CheckDerivedType()
// Real-world scenario: have a base type and want to assert/cast to derived type
IEnumerable<string> enumerable = new List<string> { "a", "b", "c" };

// Check if it's actually a List<string>
var result = await Assert.That(enumerable).IsTypeOf<List<string>, IEnumerable<string>>();
// Check if it's actually a List<string> - now with single type parameter
var result = await Assert.That(enumerable).IsTypeOf<List<string>>();

await Assert.That(result.Count).IsEqualTo(3);
}
Expand Down Expand Up @@ -366,7 +366,7 @@ public async Task IsTypeOf_IEnumerableToArray_Success()
{
IEnumerable<int> enumerable = new int[] { 10, 20, 30 };

var result = await Assert.That(enumerable).IsTypeOf<int[], IEnumerable<int>>();
var result = await Assert.That(enumerable).IsTypeOf<int[]>();

await Assert.That(result.Length).IsEqualTo(3);
await Assert.That(result[1]).IsEqualTo(20);
Expand Down Expand Up @@ -402,7 +402,7 @@ public async Task IsTypeOf_IEnumerableToHashSet_Success()
{
IEnumerable<string> enumerable = new HashSet<string> { "alpha", "beta", "gamma" };

var result = await Assert.That(enumerable).IsTypeOf<HashSet<string>, IEnumerable<string>>();
var result = await Assert.That(enumerable).IsTypeOf<HashSet<string>>();

await Assert.That(result.Count).IsEqualTo(3);
await Assert.That(result.Contains("beta")).IsTrue();
Expand Down Expand Up @@ -608,7 +608,7 @@ public async Task IsTypeOf_LinkedListFromIEnumerable_Success()
{
IEnumerable<int> enumerable = new LinkedList<int>(new[] { 10, 20, 30 });

var result = await Assert.That(enumerable).IsTypeOf<LinkedList<int>, IEnumerable<int>>();
var result = await Assert.That(enumerable).IsTypeOf<LinkedList<int>>();

await Assert.That(result.Count).IsEqualTo(3);
await Assert.That(result.First!.Value).IsEqualTo(10);
Expand Down
11 changes: 11 additions & 0 deletions TUnit.Assertions/Assertions/PropertyAssertion.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq.Expressions;
using TUnit.Assertions.Conditions;
using TUnit.Assertions.Core;

namespace TUnit.Assertions.Assertions;
Expand Down Expand Up @@ -111,6 +112,16 @@ internal PropertyAssertionResult(AssertionContext<TObject> parentContext, Assert
};
}

/// <summary>
/// Asserts that the parent object is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That(obj).HasProperty(x => x.Name).IsEqualTo("test").IsTypeOf<DerivedClass>();
/// </summary>
public TypeOfAssertion<TObject, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<TObject, TExpected>(Context);
}

/// <summary>
/// Enables await syntax by executing the property assertion and returning the parent object.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions TUnit.Assertions/Assertions/Strings/ParseAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Globalization;
using System.Reflection;
using System.Text;
using TUnit.Assertions.Conditions;
using TUnit.Assertions.Core;

namespace TUnit.Assertions.Assertions.Strings;
Expand Down Expand Up @@ -242,6 +243,16 @@ public WhenParsedIntoAssertion(
_formatProvider = formatProvider;
}

/// <summary>
/// Asserts that the parsed value is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That("123").WhenParsedInto<object>().IsTypeOf<int>();
/// </summary>
public TypeOfAssertion<T, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<T, TExpected>(Context);
}

/// <summary>
/// Specifies the format provider to use when parsing.
/// Returns a new assertion with the format provider set.
Expand Down
10 changes: 10 additions & 0 deletions TUnit.Assertions/Conditions/MemberAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ public AssertionSourceAdapter(AssertionContext<T> context)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
}

/// <summary>
/// Asserts that the value is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That(obj).Member(x => x.Property).Satisfies(val => val.IsTypeOf<string>());
/// </summary>
public TypeOfAssertion<T, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<T, TExpected>(Context);
}
}

/// <summary>
Expand Down
11 changes: 11 additions & 0 deletions TUnit.Assertions/Conditions/Wrappers/CountWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using TUnit.Assertions.Conditions;
using TUnit.Assertions.Core;

namespace TUnit.Assertions.Conditions.Wrappers;
Expand All @@ -22,6 +23,16 @@ public CountWrapper(AssertionContext<TCollection> context)

AssertionContext<TCollection> IAssertionSource<TCollection>.Context => _context;

/// <summary>
/// Not supported on CountWrapper - use IsTypeOf on the assertion source before calling HasCount().
/// </summary>
TypeOfAssertion<TCollection, TExpected> IAssertionSource<TCollection>.IsTypeOf<TExpected>()
{
throw new NotSupportedException(
"IsTypeOf is not supported after HasCount(). " +
"Use: Assert.That(value).IsTypeOf<List<int>>().HasCount().EqualTo(5)");
}

/// <summary>
/// Asserts that the collection count is equal to the expected count.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions TUnit.Assertions/Conditions/Wrappers/LengthWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using System.Text;
using TUnit.Assertions.Conditions;
using TUnit.Assertions.Core;

namespace TUnit.Assertions.Conditions.Wrappers;
Expand All @@ -19,6 +20,16 @@ public LengthWrapper(AssertionContext<string> context)

AssertionContext<string> IAssertionSource<string>.Context => _context;

/// <summary>
/// Not supported on LengthWrapper - use IsTypeOf on the assertion source before calling HasLength().
/// </summary>
TypeOfAssertion<string, TExpected> IAssertionSource<string>.IsTypeOf<TExpected>()
{
throw new NotSupportedException(
"IsTypeOf is not supported after HasLength(). " +
"Use: Assert.That(value).IsTypeOf<string>().HasLength().EqualTo(5)");
}

/// <summary>
/// Asserts that the string length is equal to the expected length.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions TUnit.Assertions/Core/IAssertionSource.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using TUnit.Assertions.Conditions;

namespace TUnit.Assertions.Core;

/// <summary>
Expand All @@ -20,4 +22,12 @@ public interface IAssertionSource<TValue> : IAssertionSource
/// Contains the evaluation context (value, timing, exceptions) and expression builder (error messages).
/// </summary>
AssertionContext<TValue> Context { get; }

/// <summary>
/// Asserts that the value is of the specified type and returns an assertion on the casted value.
/// This allows chaining additional assertions on the typed value.
/// Only available at assertion source points (initial Assert.That, or after .And/.Or).
/// Example: await Assert.That(obj).IsTypeOf&lt;string&gt;().And.HasLength(5);
/// </summary>
TypeOfAssertion<TValue, TExpected> IsTypeOf<TExpected>();
}
12 changes: 0 additions & 12 deletions TUnit.Assertions/Extensions/AssertionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,6 @@ public static LessThanAssertion<TValue> IsNegative<TValue>(
return new LessThanAssertion<TValue>(mappedContext, default(TValue)!);
}

/// <summary>
/// Asserts that the value is of the specified type and returns an assertion on the casted value.
/// This extension method variant requires specifying both TExpected and TValue type parameters.
/// Example: await Assert.That(enumerable).IsTypeOf&lt;int[], IEnumerable&lt;int&gt;&gt;();
/// </summary>
public static TypeOfAssertion<TValue, TExpected> IsTypeOf<TExpected, TValue>(
this IAssertionSource<TValue> source)
{
source.Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<TValue, TExpected>(source.Context);
}

/// <summary>
/// Asserts that the value is of the specified type (runtime Type parameter).
/// Example: await Assert.That(obj).IsOfType(typeof(string));
Expand Down
20 changes: 20 additions & 0 deletions TUnit.Assertions/Sources/AsyncDelegateAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,26 @@ public TaskIsCompletedSuccessfullyAssertion<Task> IsNotCompletedSuccessfully()
}
#endif

/// <summary>
/// Asserts that the value is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That(async () => await SomeMethodAsync()).IsTypeOf<string>();
/// </summary>
public TypeOfAssertion<object?, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<object?, TExpected>(Context);
}

/// <summary>
/// Explicit interface implementation for Task type checking.
/// Asserts that the task itself is of the specified type.
/// </summary>
TypeOfAssertion<Task, TExpected> IAssertionSource<Task>.IsTypeOf<TExpected>()
{
TaskContext.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<Task, TExpected>(TaskContext);
}

/// <summary>
/// Asserts that the async delegate throws the specified exception type (or subclass).
/// Instance method to avoid C# type inference issues with extension methods.
Expand Down
10 changes: 10 additions & 0 deletions TUnit.Assertions/Sources/AsyncFuncAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ public AsyncFuncAssertion(Func<Task<TValue?>> func, string? expression)
Context = new AssertionContext<TValue>(evaluationContext, expressionBuilder);
}

/// <summary>
/// Asserts that the async function result is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That(async () => await GetValueAsync()).IsTypeOf<string>();
/// </summary>
public TypeOfAssertion<TValue, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<TValue, TExpected>(Context);
}

/// <summary>
/// Asserts that the async function throws the specified exception type (or subclass).
/// Instance method to avoid C# type inference issues with extension methods.
Expand Down
4 changes: 2 additions & 2 deletions TUnit.Assertions/Sources/CollectionAssertionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ private protected CollectionAssertionBase(

/// <summary>
/// Asserts that the collection is of the specified type and returns an assertion on the casted value.
/// This instance method allows single type parameter usage without needing to specify the source type.
/// Example: await Assert.That(readOnlyList).IsTypeOf&lt;List&lt;double&gt;&gt;();
/// This allows chaining additional assertions on the typed value.
/// Example: await Assert.That((IEnumerable<int>)list).IsTypeOf<List<int>>().And.HasCount(5);
/// </summary>
public TypeOfAssertion<TCollection, TExpected> IsTypeOf<TExpected>()
{
Expand Down
10 changes: 10 additions & 0 deletions TUnit.Assertions/Sources/DelegateAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public DelegateAssertion(Action action, string? expression)
Context = new AssertionContext<object?>(evaluationContext, expressionBuilder);
}

/// <summary>
/// Asserts that the value is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That(() => SomeMethod()).IsTypeOf<string>();
/// </summary>
public TypeOfAssertion<object?, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<object?, TExpected>(Context);
}

/// <summary>
/// Asserts that the delegate throws the specified exception type (or subclass).
/// Instance method to avoid C# type inference issues with extension methods.
Expand Down
10 changes: 10 additions & 0 deletions TUnit.Assertions/Sources/FuncAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ public FuncAssertion(Func<TValue?> func, string? expression)
Context = new AssertionContext<TValue>(evaluationContext, expressionBuilder);
}

/// <summary>
/// Asserts that the value is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That(() => GetValue()).IsTypeOf<string>();
/// </summary>
public TypeOfAssertion<TValue, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<TValue, TExpected>(Context);
}

/// <summary>
/// Asserts that the function throws the specified exception type (or subclass).
/// Instance method to avoid C# type inference issues with extension methods.
Expand Down
20 changes: 20 additions & 0 deletions TUnit.Assertions/Sources/TaskAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@ public TaskAssertion(Task<TValue?> task, string? expression)
TaskContext = new AssertionContext<Task<TValue?>>(taskEvaluationContext, taskExpressionBuilder);
}

/// <summary>
/// Asserts that the task result is of the specified type and returns an assertion on the casted value.
/// Example: await Assert.That(GetValueAsync()).IsTypeOf<string>();
/// </summary>
public TypeOfAssertion<TValue, TExpected> IsTypeOf<TExpected>()
{
Context.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<TValue, TExpected>(Context);
}

/// <summary>
/// Explicit interface implementation for Task&lt;TValue?&gt; type checking.
/// Asserts that the task itself is of the specified type.
/// </summary>
TypeOfAssertion<Task<TValue?>, TExpected> IAssertionSource<Task<TValue?>>.IsTypeOf<TExpected>()
{
TaskContext.ExpressionBuilder.Append($".IsTypeOf<{typeof(TExpected).Name}>()");
return new TypeOfAssertion<Task<TValue?>, TExpected>(TaskContext);
}

/// <summary>
/// Asserts that the async function throws the specified exception type (or subclass).
/// Instance method to avoid C# type inference issues with extension methods.
Expand Down
Loading
Loading