> ## Documentation Index
> Fetch the complete documentation index at: https://easyaf.dev/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?

<CardGroup cols={2}>
  <Card title="Speed" icon="bolt">
    Tests run instantly by reading from disk instead of making network calls
  </Card>

  <Card title="Reliability" icon="shield-check">
    Tests pass consistently without API availability or rate limiting issues
  </Card>

  <Card title="Offline Testing" icon="plane">
    Run your test suite anywhere, even without internet access
  </Card>

  <Card title="CI/CD Friendly" icon="robot">
    No secrets or API credentials needed in your build pipeline
  </Card>
</CardGroup>

## 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

<Steps>
  <Step title="Install the package">
    ```bash theme={"dark"}
    dotnet add package Breakdance.Assemblies
    ```
  </Step>

  <Step title="Create a folder for snapshot files">
    ```
    MyProject.Tests/
      ResponseSnapshots/
        (captured responses go here)
    ```
  </Step>

  <Step title="Capture responses">
    Run your tests with the Capture handler to record real API responses.
  </Step>

  <Step title="Switch to Replay handler">
    Change to the Replay handler for subsequent test runs.
  </Step>
</Steps>

## 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.
</Tip>

## 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.
</Note>

## 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

<AccordionGroup>
  <Accordion title="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>

  <Accordion title="Refresh snapshots periodically">
    API responses change over time. Set a reminder to refresh your captured responses
    monthly or when API versions change.
  </Accordion>

  <Accordion title="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>

  <Accordion title="Use meaningful directory names">
    Organize response files by feature or test scenario for easier maintenance:

    ```
    ResponseSnapshots/
      user-management/
      order-processing/
      authentication/
    ```
  </Accordion>

  <Accordion title="Handle concurrent writes">
    `ResponseSnapshotCaptureHandler` includes retry logic for file locking when
    multiple tests run in parallel. No additional configuration needed.
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="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>

  <Accordion title="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>

  <Accordion title="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>
    ```
  </Accordion>
</AccordionGroup>

## 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

<CardGroup cols={2}>
  <Card title="Request Snapshots" icon="file-code" href="/breakdance/guides/web/snapshots/requests">
    Write API tests using the `.http` file format
  </Card>

  <Card title="Snapshots Overview" icon="camera" href="/breakdance/guides/web/snapshots/index">
    Learn about the Test Real Things philosophy
  </Card>
</CardGroup>
