Skip to content

Latest commit

 

History

History
431 lines (303 loc) · 13 KB

File metadata and controls

431 lines (303 loc) · 13 KB

TempFile

A temporary file helper for tests that automatically cleans up files.

Features

  • Creates unique temporary files in %TEMP%\VerifyTempFiles\{Path.GetRandomFileName()}
  • Automatic cleanup on dispose
  • Implicit conversion to string and FileInfo
  • Thread-safe file creation
  • Removes orphaned files older than 24 hours

Usage

[Fact]
public void Usage()
{
    using var temp = new TempFile();

    File.WriteAllText(temp, "content");

    // file automatically deleted here
}

snippet source | anchor

Orphaned files

Orphaned files can occur in the following scenario

  • A breakpoint is set in a test that uses TempFile
  • Debugger is launched and that breakpoint is hit
  • Debugger is force stopped, resulting in the TempFile.Dispose() not being executed

Path Property

Contains the full path to the temporary file.

[Fact]
public void PathProperty()
{
    using var temp = new TempFile();
    var path = temp.Path;
    Assert.True(Path.IsPathRooted(path));
}

snippet source | anchor

Implicit Conversion

Implicit Conversion is helpful as it allows a TempFile instance to be passed to directly to method that takes a string or a FileInfo.

String Implicit Conversion

TempFile can be implicitly converted to a string:

[Fact]
public void StringConversion()
{
    using var temp = new TempFile();

    File.WriteAllText(temp, "content");

    // implicit conversion to string
    string path = temp;
    var content = File.ReadAllText(path);
    Trace.WriteLine(content);
}

snippet source | anchor

FileInfo Implicit Conversion

TempFile can be implicitly converted to a FileInfo:

[Fact]
public void FileInfoConversion()
{
    using var temp = new TempFile();

    // implicit conversion to FileInfo
    FileInfo info = temp;

    var directoryName = info.DirectoryName;
    Trace.WriteLine(directoryName);
}

snippet source | anchor

Info Property

TempFile has a convenience Info that can be used to access all the FileInfo members:

[Fact]
public void InfoProperty()
{
    using var temp = new TempFile();

    var directoryName = temp.Info.DirectoryName;

    Trace.WriteLine(directoryName);
}

snippet source | anchor

TempFile RootDirectory Property

Allows access to the root directory for all TempFile instances:

[Fact]
public void RootDirectory() =>
    // Accessing the root directory for all TempDirectory instances
    Trace.WriteLine(TempFile.RootDirectory);

snippet source | anchor

Cleanup Behavior

The dispose cleans up the current instance.

The static constructor automatically:

  1. Ensures the root directory %TEMP%\VerifyTempFiles exists
  2. Deletes files not modified in the last 24 hours
  3. Runs once per application domain

Thread Safety

Each instance creates a unique file using Path.GetRandomFileName(), making concurrent usage safe.

VerifyFile

TempFile is compatible with VerifyFile.

[Fact]
public async Task VerifyFileInstance()
{
    using var file = new TempFile("txt");
    await File.WriteAllTextAsync(file, "test");
    await VerifyFile(file);
}

snippet source | anchor

TempFile paths are scrubbed

[Fact]
public async Task Scrubbing()
{
    using var temp = new TempFile();

    await Verify(new
    {
        PropertyWithTempPath = temp,
        TempInStringProperty = $"The path is {temp}"
    });
}

snippet source | anchor

Result:

{
  PropertyWithTempPath: {TempFile},
  TempInStringProperty: The path is {TempFile}
}

snippet source | anchor

Create Method

Creates a new temporary file with optional extension and encoding.

using var temp = TempFile.Create();

File.WriteAllText(temp, "content");

// file automatically deleted here

snippet source | anchor

With Extension

Create a temporary file with a specific extension:

using var temp = TempFile.Create(".txt");

File.WriteAllText(temp, "content");

snippet source | anchor

With Encoding

Create a temporary file with a specific text encoding and BOM:

using var temp = TempFile.Create(".txt", Encoding.UTF8);

File.Exists(temp.Path);

snippet source | anchor

CreateText Method

Creates a new temporary file with text content asynchronously.

using var temp = await TempFile.CreateText("Hello, World!");

var content = await File.ReadAllTextAsync(temp);
Assert.Equal("Hello, World!", content);

snippet source | anchor

With Extension

var json = """
           {
             "name": "test",
             "value": 123
           }
           """;

using var temp = await TempFile.CreateText(json, ".json");

var content = await File.ReadAllTextAsync(temp);
Assert.Equal(json, content);

snippet source | anchor

With Encoding

Create a text file with specific encoding:

using var temp = await TempFile.CreateText(
    "Content with special chars: äöü",
    ".txt",
    Encoding.UTF8);

var content = await File.ReadAllTextAsync(temp, Encoding.UTF8);
Assert.Equal("Content with special chars: äöü", content);

snippet source | anchor

CreateBinary Method

Creates a new temporary file with binary content asynchronously.

byte[] data = [0x01, 0x02, 0x03, 0x04];

using var temp = await TempFile.CreateBinary(data);

var readData = await File.ReadAllBytesAsync(temp);
Assert.Equal(data, readData);

snippet source | anchor

With Extension

byte[] data = [0x01, 0x02, 0x03, 0x04];
using var temp = await TempFile.CreateBinary(data, ".bin");

snippet source | anchor

Debugging

Given TempFile deletes the file on test completion (even failure), it can be difficult to debug what caused the failure.

There are several approaches that can be used to inspect the contents of the temp file.

The below should be considered temporary approaches to be used only during debugging. The code should not be committed to source control.

No Using

Omitting the using for the TempFile will prevent the temp file from being deleted when the test finished.

[Fact(Explicit = true)]
public void NoUsing()
{
    //using var temp = new TempFile();
    var temp = new TempFile();

    File.WriteAllText(temp, "content");

    Debug.WriteLine(temp);
}

snippet source | anchor

The file can then be manually inspected.

OpenExplorerAndDebug

Opens the temporary file in the system file explorer and breaks into the debugger.

[Fact(Explicit = true)]
public void OpenExplorerAndDebug()
{
    using var temp = new TempFile();

    File.WriteAllText(temp, "content");

    // this is temporary debugging code and should not be commited to source control
    temp.OpenExplorerAndDebug();
}

snippet source | anchor

This method is designed to help debug tests by enabling the inspection of the contents of the temporary file while the test is paused. It performs two actions:

  1. Opens the file in the file explorer - Launches the system's default file explorer (Explorer on Windows, Finder on macOS) and navigates to the temporary file
  2. Breaks into the debugger - If a debugger is already attached, execution breaks at this point. If no debugger is attached, it attempts to launch one. This prevents the file being clean up by the TempFile.Dispose().

This enables examination of the file contents at a specific point during test execution.

Supported Platforms:

  • Windows (uses explorer.exe)
  • macOS (uses open)

Throws an exception if used on a build server. Uses DiffEngine.BuildServerDetector.Detected.

Rider

For Debugger.Launch(); to work correctly in JetBrains Rider use Set Rider as the default debugger.