Repository navigation
State Persistence
Save, restore, and automatically persist container state between test runs.
The emulator provides three levels of state persistence:
| Feature | What it does | Use case |
|---|---|---|
| Manual Export/Import | Save/load container state as JSON on demand | Snapshots, seed data files, debugging |
| Automatic Persistence | Auto-save on disposal, auto-load on creation | Preserving data between test runs |
| Point-in-Time Restore | Roll back to any previous state via change feed replay | Test isolation, debugging, simulating PITR |
Export the current container state as JSON and re-import it later.
// Export to string
string json = container.ExportState();
// Export to file
container.ExportStateToFile("test-data/snapshot.json");
// Import from string (replaces all data)
container.ImportState(json);
// Import from file
container.ImportStateFromFile("test-data/snapshot.json");
// Utilities
int count = container.ItemCount;
container.ClearItems(); // Remove everythingThe exported format is a simple JSON object with an items array:
{
"items": [
{ "id": "1", "partitionKey": "pk1", "name": "Alice", "_etag": "\"guid\"", "_ts": 1234567890 },
{ "id": "2", "partitionKey": "pk2", "name": "Bob", "_etag": "\"guid\"", "_ts": 1234567890 }
]
}-
ImportStateclears all existing data first — it's a full replacement, not a merge. -
New ETags and timestamps are generated on import. The original
_etagand_tsvalues in the JSON are overwritten with fresh values. - Change feed is not exported or imported. The change feed starts fresh after import. Writes after import do appear in the change feed.
- Unique key constraints are validated on import — items violating the container's unique key policy will throw.
Check seed files into source control for repeatable test data:
// Direct instantiation
using var cosmos = InMemoryCosmos.Create("orders", "/customerId");
cosmos.ImportStateFromFile("TestData/orders-seed.json");
// Via Dependency Injection callback
services.UseInMemoryCosmosDB(options =>
{
options.AddContainer("orders", "/customerId");
options.OnHandlerCreated = (name, handler) =>
{
handler.BackingContainer.ImportStateFromFile("TestData/orders-seed.json");
};
});For more seeding patterns, see Seeding Data.
Added in v2.0.70. Automatically save and restore container state between test runs with zero
manual ExportState/ImportState calls.
| Step | What happens |
|---|---|
| Startup | If a state file exists for the container, ImportState loads it automatically |
| First run | No file exists — container starts empty (no error) |
| Disposal | Container saves its state via ExportState to the file |
| Next run | Container loads the previously saved state |
Set StatePersistenceDirectory on the Dependency Injection options. Each container gets its own file.
builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosDB(options =>
{
options.AddContainer("orders", "/customerId");
options.AddContainer("customers", "/id");
options.StatePersistenceDirectory = "./test-state";
});
});Files: test-state/in-memory-db_orders.json, test-state/in-memory-db_customers.json
builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosContainers(options =>
{
options.AddContainer("orders", "/customerId");
options.StatePersistenceDirectory = "./test-state";
});
});Files: test-state/orders.json
builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosDB<EmployeeCosmosClient>(options =>
{
options.AddContainer("employees", "/departmentId");
options.StatePersistenceDirectory = "./test-state";
});
});Files: test-state/in-memory-db_employees.json
For unit tests without Dependency Injection, use the configureContainer callback to set StateFilePath:
public class OrderRepositoryTests : IDisposable
{
private readonly InMemoryCosmosResult _cosmos;
public OrderRepositoryTests()
{
_cosmos = InMemoryCosmos.Create("orders", "/customerId",
configureContainer: c =>
{
c.StateFilePath = "./test-state/orders.json";
c.LoadPersistedState(); // Loads if file exists, no-op otherwise
});
}
[Fact]
public async Task CreateOrder_PersistsBetweenRuns()
{
await _cosmos.Container.CreateItemAsync(
new Order { Id = "order-1", CustomerId = "cust-1" },
new PartitionKey("cust-1"));
}
public void Dispose()
{
_cosmos.Dispose(); // Saves state to ./test-state/orders.json
}
}| Dependency Injection Extension | File name pattern | Example |
|---|---|---|
UseInMemoryCosmosDB |
{DatabaseName}_{ContainerName}.json |
in-memory-db_orders.json |
UseInMemoryCosmosDB<T> |
{DatabaseName}_{ContainerName}.json |
in-memory-db_employees.json |
UseInMemoryCosmosContainers |
{ContainerName}.json |
orders.json |
Direct (StateFilePath) |
Whatever you set | ./my-state/orders.json |
- Directory creation: The directory is created automatically on save if it doesn't exist.
-
ETags and timestamps:
ImportStategenerates new ETags and timestamps on load. Items get fresh system properties each run. - Change feed: Not persisted. The change feed starts fresh each run.
- Multiple containers: Each container gets its own file. Multiple containers in the same persistence directory coexist without conflict.
-
Disposal cascade: When using
InMemoryCosmos.Builder().Build(), disposing the result cascadesDisposeto all containers. When usingFakeCosmosHandler, disposing the handler cascades to the backing container.
If you don't want state files committed to source control:
# In-memory emulator persisted state
test-state/If you do want to commit them (e.g. as seed data that evolves over time), that works too — the
files are standard JSON in the same {"items":[...]} format used by ExportState.
Restore a container to its state at any previous point in time. This replays the internal change feed up to the specified timestamp, reconstructing the exact state of every item.
// Create and modify data over time
await container.CreateItemAsync(item1, new PartitionKey("pk"));
var restorePoint = DateTimeOffset.UtcNow;
await container.CreateItemAsync(item2, new PartitionKey("pk"));
await container.DeleteItemAsync<MyDoc>("item1", new PartitionKey("pk"));
// Roll back to the restore point — item1 reappears, item2 is gone
container.RestoreToPointInTime(restorePoint);- Debugging — inspect container state at a specific moment
- Test isolation — restore to a known baseline between test cases
- Simulating Cosmos DB continuous backup — test PITR-dependent recovery logic
- The restore replays the change feed, so it only works for changes made during the lifetime of the container instance.
- The change feed is not persisted across
ExportState/ImportStatecalls or between test runs. -
ClearItems()resets the change feed — PITR is not available after clearing. - If
MaxChangeFeedSizeeviction has removed older entries, PITR may produce incomplete results. SetMaxChangeFeedSize = 0to disable eviction if you rely on PITR across large numbers of writes.
| Method / Property | Description |
|---|---|
ExportState() |
Returns all items as formatted JSON string |
ImportState(json) |
Replaces all data from JSON string |
ExportStateToFile(path) |
Saves state to file |
ImportStateFromFile(path) |
Loads state from file |
StateFilePath |
When set, Dispose() auto-saves and LoadPersistedState() auto-loads |
LoadPersistedState() |
Loads from StateFilePath if file exists (no-op if missing) |
Dispose() |
Saves to StateFilePath if set |
RestoreToPointInTime(time) |
Replays change feed to restore to a point in time |
ClearItems() |
Removes all items, ETags, timestamps, and change feed |
ItemCount |
Number of items currently stored |
StatePersistenceDirectory |
Dependency Injection option — sets StateFilePath on each container automatically |
- Seeding Data — all data seeding patterns including Dependency Injection callbacks and bulk loading
- Features — full feature reference
- API Reference — detailed method signatures
- Unit Testing — direct container testing patterns
- Setup Guide — Dependency Injection extension patterns
Getting Started
Integration & Dependency Injection
Data Management
Reference
Help