Skip to main content
Response snapshots let you capture actual HTTP responses from real APIs, then replay them during test execution. Instead of maintaining hand-crafted mock responses that might drift from reality, you test against the same data your production code will encounter.

Why Use Response Snapshots?

Speed

Tests run instantly by reading from disk instead of making network calls

Reliability

Tests pass consistently without API availability or rate limiting issues

Offline Testing

Run your test suite anywhere, even without internet access

CI/CD Friendly

No secrets or API credentials needed in your build pipeline

How It Works

Breakdance provides two DelegatingHandler classes for the snapshot workflow:
HandlerPurpose
ResponseSnapshotCaptureHandlerMakes real HTTP calls and saves responses to files
ResponseSnapshotReplayHandlerReads responses from files instead of making HTTP calls
The workflow:
  1. Run tests once with the Capture handler to record real responses
  2. Check captured response files into source control
  3. Run tests with the Replay handler for fast, deterministic execution

Quick Start

1

Install the package

dotnet add package Breakdance.Assemblies
2

Create a folder for snapshot files

MyProject.Tests/
  ResponseSnapshots/
    (captured responses go here)
3

Capture responses

Run your tests with the Capture handler to record real API responses.
4

Switch to Replay handler

Change to the Replay handler for subsequent test runs.

Capturing Responses

Use ResponseSnapshotCaptureHandler to capture real API responses:
ApiCaptureTests.cs
using CloudNimble.Breakdance.Assemblies.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net.Http;
using System.Threading.Tasks;

[TestClass]
public class ApiCaptureTests
{
    [TestMethod]
    public async Task CaptureUserEndpoint()
    {
        // Create handler that will save responses to ResponseSnapshots folder
        var handler = new ResponseSnapshotCaptureHandler("ResponseSnapshots")
        {
            InnerHandler = new HttpClientHandler()
        };

        var client = new HttpClient(handler);

        // This makes a real HTTP call and saves the response
        var response = await client.GetAsync("https://jsonplaceholder.typicode.com/users");

        Assert.IsTrue(response.IsSuccessStatusCode);
    }
}
After running this test, you’ll have:
ResponseSnapshots/
  jsonplaceholder.typicode.com/
    users.json
Run capture tests once, then check the ResponseSnapshots folder into source control. This lets your entire team (and CI) run tests without API access.

Replaying Responses

Switch to ResponseSnapshotReplayHandler to serve captured responses:
ApiTests.cs
using CloudNimble.Breakdance.Assemblies.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net.Http;
using System.Threading.Tasks;

[TestClass]
public class ApiTests
{
    [TestMethod]
    public async Task GetUsers_FromSnapshot()
    {
        // Create handler that reads from ResponseSnapshots folder
        var handler = new ResponseSnapshotReplayHandler("ResponseSnapshots");
        var client = new HttpClient(handler);

        // This reads from file, no network call
        var response = await client.GetAsync("https://jsonplaceholder.typicode.com/users");

        Assert.IsTrue(response.IsSuccessStatusCode);

        var content = await response.Content.ReadAsStringAsync();
        Assert.IsFalse(string.IsNullOrWhiteSpace(content));
    }
}
The Replay handler throws InvalidOperationException if a snapshot file doesn’t exist. This helps catch missing captures early.

File Organization

Responses are organized by host and path:
Request URLFile Path
https://api.example.com/usersResponseSnapshots/api.example.com/users.json
https://api.example.com/users/123ResponseSnapshots/api.example.com/users/123.json
https://api.example.com/v1/items?type=activeResponseSnapshots/api.example.com/v1/items.json
The file extension is determined by the response’s Content-Type:
  • application/json.json
  • application/xml or text/xml.xml
  • text/html.html
  • Others → .txt

Using with DotHttpTestBase

Combine response snapshots with request snapshots (.http files) for comprehensive testing:
using CloudNimble.Breakdance.DotHttp;
using CloudNimble.Breakdance.Assemblies.Http;

public class CachedApiTests : DotHttpTestBase
{
    protected override HttpMessageHandler CreateHttpMessageHandler()
    {
        return new ResponseSnapshotReplayHandler("ResponseSnapshots");
    }

    [TestMethod]
    public async Task GetUsers_Test()
    {
        SetVariable("baseUrl", "https://api.example.com");

        var request = new DotHttpRequest
        {
            Method = "GET",
            Url = "{{baseUrl}}/users"
        };

        // Response served from ResponseSnapshots/api.example.com/users.json
        var response = await SendRequestAsync(request);
        await DotHttpAssertions.AssertValidResponseAsync(response);
    }
}

Capture/Replay Pattern

A common pattern is to have separate capture and test modes:
public abstract class ApiTestBase : DotHttpTestBase
{
    // Toggle this to switch between capture and replay modes
    protected virtual bool CaptureMode => false;

    protected override HttpMessageHandler CreateHttpMessageHandler()
    {
        if (CaptureMode)
        {
            return new ResponseSnapshotCaptureHandler("ResponseSnapshots")
            {
                InnerHandler = new HttpClientHandler()
            };
        }

        return new ResponseSnapshotReplayHandler("ResponseSnapshots");
    }
}

// For normal test runs (replayed responses)
[TestClass]
public class UserApiTests : ApiTestBase
{
    // Uses replayed responses by default
}

// For capturing fresh responses
[TestClass]
[Ignore("Run manually to refresh response snapshots")]
public class UserApiCapture : ApiTestBase
{
    protected override bool CaptureMode => true;
}

Environment-Specific Responses

Capture responses per environment for different test scenarios:
public class MultiEnvironmentTests : DotHttpTestBase
{
    private readonly string _environment;

    public MultiEnvironmentTests()
    {
        _environment = Environment.GetEnvironmentVariable("TEST_ENV") ?? "default";
    }

    protected override HttpMessageHandler CreateHttpMessageHandler()
    {
        var folder = $"ResponseSnapshots/{_environment}";
        return new ResponseSnapshotReplayHandler(folder);
    }
}
Directory structure:
ResponseSnapshots/
  default/
    api.example.com/
      users.json
  error-scenarios/
    api.example.com/
      users.json  (contains error response)
  empty-results/
    api.example.com/
      users.json  (contains empty array)

Testing Edge Cases

Create response files manually to test specific scenarios:
ResponseSnapshots/error-scenarios/api.example.com/users.json
{
    "error": "Internal server error",
    "code": "INTERNAL_ERROR"
}
[TestMethod]
public async Task GetUsers_HandlesError_Gracefully()
{
    // Point to error response file
    var handler = new ResponseSnapshotReplayHandler("ResponseSnapshots/error-scenarios");
    var client = new HttpClient(handler);

    var response = await client.GetAsync("https://api.example.com/users");
    var content = await response.Content.ReadAsStringAsync();

    Assert.IsTrue(content.Contains("error"));
}

Best Practices

Response snapshot files should be committed alongside your tests. This ensures everyone on the team and your CI pipeline can run tests without API access.
API responses change over time. Set a reminder to refresh your captured responses monthly or when API versions change.
Be careful not to capture responses containing secrets, tokens, or personal data. Either sanitize the response files or use test accounts with non-sensitive data.
Organize response files by feature or test scenario for easier maintenance:
ResponseSnapshots/
  user-management/
  order-processing/
  authentication/
ResponseSnapshotCaptureHandler includes retry logic for file locking when multiple tests run in parallel. No additional configuration needed.

Troubleshooting

The Replay handler throws InvalidOperationException with the expected file path. Check:
  • The file exists at the expected path
  • The URL matches exactly (including query string handling)
  • The ResponseSnapshots folder is copied to test output (set Copy to Output Directory)
The handler sets Content-Type based on file extension. If you need a specific Content-Type, ensure your file has the correct extension (.json, .xml, etc.).
Ensure snapshot files are included in your test project:
<ItemGroup>
  <None Include="ResponseSnapshots\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Migration from TestCache Classes

If you’re upgrading from an earlier version of Breakdance, the following classes have been renamed:
Old NameNew Name
TestCacheWriteDelegatingHandlerResponseSnapshotCaptureHandler
TestCacheReadDelegatingHandlerResponseSnapshotReplayHandler
TestCacheDelegatingHandlerBaseResponseSnapshotHandlerBase
ResponseFilesPath propertyResponseSnapshotsPath property
The old class names are still available but marked as [Obsolete] and will be removed in a future major version.