> ## Documentation Index
> Fetch the complete documentation index at: https://easyaf.nimbleapps.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Response Snapshots

> Capture real HTTP responses from APIs and replay them in tests for fast, deterministic, and offline-capable testing.

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?

> [card] Speed
>
> Tests run instantly by reading from disk instead of making network calls

> [card] Reliability
>
> Tests pass consistently without API availability or rate limiting issues

> [card] Offline Testing
>
> Run your test suite anywhere, even without internet access

> [card] 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:

| Handler | Purpose |
| - | - |
| `ResponseSnapshotCaptureHandler` | Makes real HTTP calls and saves responses to files |
| `ResponseSnapshotReplayHandler` | Reads 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**

```bash theme={"dark"}
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:

```csharp ApiCaptureTests.cs theme={"dark"}
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
```

> [!TIP]
>
> 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:

```csharp ApiTests.cs theme={"dark"}
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));
    }
}
```

> [!NOTE]
>
> 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 URL | File Path |
| - | - |
| `https://api.example.com/users` | `ResponseSnapshots/api.example.com/users.json` |
| `https://api.example.com/users/123` | `ResponseSnapshots/api.example.com/users/123.json` |
| `https://api.example.com/v1/items?type=active` | `ResponseSnapshots/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:

```csharp theme={"dark"}
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:

```csharp theme={"dark"}
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:

```csharp theme={"dark"}
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:

```json ResponseSnapshots/error-scenarios/api.example.com/users.json theme={"dark"}
{
    "error": "Internal server error",
    "code": "INTERNAL_ERROR"
}
```

```csharp theme={"dark"}
[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

> [accordion] Check response files into source control
>
> 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.

> [accordion] Refresh snapshots periodically
>
> API responses change over time. Set a reminder to refresh your captured responses
> monthly or when API versions change.

> [accordion] Don't capture sensitive data
>
> 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.

> [accordion] Use meaningful directory names
>
> Organize response files by feature or test scenario for easier maintenance:
>
> ```
> ResponseSnapshots/
>   user-management/
>   order-processing/
>   authentication/
> ```

> [accordion] Handle concurrent writes
>
> `ResponseSnapshotCaptureHandler` includes retry logic for file locking when
> multiple tests run in parallel. No additional configuration needed.

## Troubleshooting

> [accordion] Response snapshot file not found
>
> 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)

> [accordion] Wrong Content-Type
>
> 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.).

> [accordion] Tests pass locally but fail in CI
>
> Ensure snapshot files are included in your test project:
>
> ```xml theme={"dark"}
> <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 Name | New Name |
| - | - |
| `TestCacheWriteDelegatingHandler` | `ResponseSnapshotCaptureHandler` |
| `TestCacheReadDelegatingHandler` | `ResponseSnapshotReplayHandler` |
| `TestCacheDelegatingHandlerBase` | `ResponseSnapshotHandlerBase` |
| `ResponseFilesPath` property | `ResponseSnapshotsPath` property |

The old class names are still available but marked as `[Obsolete]` and will be removed in a future major version.

## Related Resources

> [card] [Request Snapshots](https://easyaf.nimbleapps.cloud/breakdance/guides/web/snapshots/requests.md)
>
> Write API tests using the `.http` file format

> [card] [Snapshots Overview](https://easyaf.nimbleapps.cloud/breakdance/guides/web/snapshots/index.md)
>
> Learn about the Test Real Things philosophy


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.