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
10 changes: 10 additions & 0 deletions TUnit.Core/Interfaces/ITestOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public interface ITestOutput
/// <param name="artifact">The artifact to attach</param>
void AttachArtifact(Artifact artifact);

/// <summary>
/// Attaches a file as an artifact to this test.
/// Artifacts are preserved after test execution.
/// Thread-safe for concurrent calls.
/// </summary>
/// <param name="filePath">The path to the file to attach</param>
/// <param name="displayName">Optional display name for the artifact. Defaults to the file name.</param>
/// <param name="description">Optional description of the artifact</param>
void AttachArtifact(string filePath, string? displayName = null, string? description = null);

/// <summary>
/// Gets all standard output written during test execution as a single string.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions TUnit.Core/TestContext.Output.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ void ITestOutput.AttachArtifact(Artifact artifact)
_artifactsBag.Add(artifact);
}

void ITestOutput.AttachArtifact(string filePath, string? displayName, string? description)
{
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The filePath parameter should be validated for null or whitespace before creating a FileInfo object. Consider adding validation like ArgumentException.ThrowIfNullOrWhiteSpace(filePath) at the beginning of the method to provide a clear error message when an invalid path is provided, rather than allowing a potentially confusing exception from the FileInfo constructor.

Suggested change
{
{
ArgumentException.ThrowIfNullOrWhiteSpace(filePath);

Copilot uses AI. Check for mistakes.
var fileInfo = new FileInfo(filePath);
_artifactsBag.Add(new Artifact
{
File = fileInfo,
DisplayName = displayName ?? fileInfo.Name,
Description = description
});
}

string ITestOutput.GetStandardOutput() => GetOutput();
string ITestOutput.GetErrorOutput() => GetOutputError();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,7 @@ namespace .Interfaces
.TextWriter StandardOutput { get; }
.<.Timing> Timings { get; }
void AttachArtifact(.Artifact artifact);
void AttachArtifact(string filePath, string? displayName = null, string? description = null);
string GetErrorOutput();
string GetStandardOutput();
void RecordTiming(.Timing timing);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,7 @@ namespace .Interfaces
.TextWriter StandardOutput { get; }
.<.Timing> Timings { get; }
void AttachArtifact(.Artifact artifact);
void AttachArtifact(string filePath, string? displayName = null, string? description = null);
string GetErrorOutput();
string GetStandardOutput();
void RecordTiming(.Timing timing);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,7 @@ namespace .Interfaces
.TextWriter StandardOutput { get; }
.<.Timing> Timings { get; }
void AttachArtifact(.Artifact artifact);
void AttachArtifact(string filePath, string? displayName = null, string? description = null);
string GetErrorOutput();
string GetStandardOutput();
void RecordTiming(.Timing timing);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,7 @@ namespace .Interfaces
.TextWriter StandardOutput { get; }
.<.Timing> Timings { get; }
void AttachArtifact(.Artifact artifact);
void AttachArtifact(string filePath, string? displayName = null, string? description = null);
string GetErrorOutput();
string GetStandardOutput();
void RecordTiming(.Timing timing);
Expand Down
18 changes: 18 additions & 0 deletions TUnit.TestProject/TestArtifactTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,22 @@ public void Artifact_Test()
DisplayName = "Blah!"
});
}

[Test]
public void Artifact_Test_Simple_Overload()
{
// Simple overload - file name is used as display name
TestContext.Current!.Output.AttachArtifact("Data/Zip.zip");
}

[Test]
public void Artifact_Test_Simple_Overload_With_Name()
{
// Simple overload with custom display name and description
TestContext.Current!.Output.AttachArtifact(
"Data/Zip.zip",
displayName: "Test Zip File",
description: "A sample zip file for testing"
);
}
}
28 changes: 24 additions & 4 deletions docs/docs/test-lifecycle/artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,42 @@ Attach files to individual tests using `TestContext.Current.Output.AttachArtifac

### Basic Usage

The simplest way to attach an artifact is by providing just the file path:

```csharp
[Test]
public async Task MyIntegrationTest()
{
// Perform your test logic
var result = await PerformOperation();

// Attach an artifact to this specific test

// Attach an artifact using the simple overload
TestContext.Current!.Output.AttachArtifact("path/to/logfile.log");

// Or with a custom display name and description
TestContext.Current!.Output.AttachArtifact(
"path/to/logfile.log",
displayName: "Application Logs",
description: "Logs captured during test execution"
);

await Assert.That(result).IsEqualTo(expected);
}
```

For more control, you can create an `Artifact` object directly:

```csharp
[Test]
public async Task MyIntegrationTest()
{
// Attach an artifact using the full Artifact object
TestContext.Current!.Output.AttachArtifact(new Artifact
{
File = new FileInfo("path/to/logfile.log"),
DisplayName = "Application Logs",
Description = "Logs captured during test execution"
});

await Assert.That(result).IsEqualTo(expected);
}
```

Expand Down
Loading