Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Fix ArgumentDisplayFormatter being ignored - invalidate cache after d…
…iscovery

Co-authored-by: thomhurst <[email protected]>
  • Loading branch information
Copilot and thomhurst committed Oct 27, 2025
commit f01c99f7c74887c1a39b4a8caf2cefb24c4a9bf1
9 changes: 9 additions & 0 deletions TUnit.Core/TestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ public string GetDisplayName()
return _cachedDisplayName;
}

/// <summary>
/// Clears the cached display name, forcing it to be recomputed on next access.
/// This is called after discovery event receivers run to ensure custom argument formatters are applied.
/// </summary>
internal void InvalidateDisplayNameCache()
{
_cachedDisplayName = null;
}

public Dictionary<string, object?> ObjectBag => _testBuilderContext.ObjectBag;

public bool ReportResult { get; set; } = true;
Expand Down
5 changes: 5 additions & 0 deletions TUnit.Engine/Building/TestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,11 @@ public async Task<AbstractExecutableTest> BuildTestAsync(TestMetadata metadata,

await InvokeDiscoveryEventReceiversAsync(context);

// Clear the cached display name after discovery events
// This ensures that ArgumentDisplayFormatterAttribute and similar attributes
// have a chance to register their formatters before the display name is finalized
context.InvalidateDisplayNameCache();

return test;
}

Expand Down
72 changes: 72 additions & 0 deletions TUnit.TestProject/ArgumentDisplayFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using TUnit.TestProject.Attributes;

namespace TUnit.TestProject;

[EngineTest(ExpectedResult.Pass)]
public class ArgumentDisplayFormatterTests
{
[Test]
[MethodDataSource(nameof(Data1))]
[ArgumentDisplayFormatter<FooFormatter>]
public async Task FormatterShouldBeAppliedToMethodDataSource(Foo foo)
{
// Verify the formatter was applied by checking the display name
var displayName = TestContext.Current!.GetDisplayName();
await Assert.That(displayName).IsEqualTo("FormatterShouldBeAppliedToMethodDataSource(FooFormatterValue)");
}

[Test]
[Arguments(1, 2, 3)]
[ArgumentDisplayFormatter<IntFormatter>]
public async Task FormatterShouldBeAppliedToArguments(int a, int b, int c)
{
// Verify the formatter was applied by checking the display name
var displayName = TestContext.Current!.GetDisplayName();
await Assert.That(displayName).IsEqualTo("FormatterShouldBeAppliedToArguments(INT:1, INT:2, INT:3)");
}

[Test]
[MethodDataSource(nameof(DataWithException))]
[ArgumentDisplayFormatter<BarFormatter>]
public async Task FormatterShouldPreventExceptionInToString(Bar bar)
{
// The Bar.ToString() throws, but the formatter should prevent that
var displayName = TestContext.Current!.GetDisplayName();
await Assert.That(displayName).IsEqualTo("FormatterShouldPreventExceptionInToString(BarFormatterValue)");
}

public static IEnumerable<Foo> Data1() => [new Foo()];

public static IEnumerable<Bar> DataWithException() => [new Bar()];
}

public class Foo
{
public override string ToString() => throw new Exception("Foo.ToString should not be called");
}

public class Bar
{
public override string ToString() => throw new Exception("Bar.ToString should not be called");
}

public class FooFormatter : ArgumentDisplayFormatter
{
public override bool CanHandle(object? value) => value is Foo;

public override string FormatValue(object? value) => "FooFormatterValue";
}

public class BarFormatter : ArgumentDisplayFormatter
{
public override bool CanHandle(object? value) => value is Bar;

public override string FormatValue(object? value) => "BarFormatterValue";
}

public class IntFormatter : ArgumentDisplayFormatter
{
public override bool CanHandle(object? value) => value is int;

public override string FormatValue(object? value) => $"INT:{value}";
}
Loading