-
Notifications
You must be signed in to change notification settings - Fork 2
Setup Guide
Step-by-step guide for wiring CosmosDB.InMemoryEmulator into your test projects — Dependency Injection patterns, custom factory interfaces, and direct instantiation. For installation, see Getting Started. For help choosing the right approach, see Choosing Your Approach. For a cookbook of test examples (CRUD, queries, patches, test isolation), see Unit Testing.
The emulator provides three Dependency Injection extension methods on IServiceCollection, plus patterns for custom factory interfaces and direct instantiation:
| Pattern | Method / Approach | Purpose |
|---|---|---|
| 1 | UseInMemoryCosmosDB() |
Client + Container — the most common setup |
| 2 | UseInMemoryCosmosDB<TClient>() |
Typed CosmosClient subclass |
| 3 | UseInMemoryCosmosDB() |
Client only — repos call GetContainer() directly |
| 4 | UseInMemoryCosmosContainers() |
Container only — no CosmosClient replacement |
| 5 |
InMemoryCosmos.Create(...) / InMemoryCosmos.Builder()
|
Custom factory interface |
All patterns require zero production code changes. Your production Startup.cs / Program.cs stays exactly as-is — including .ToFeedIterator(), LINQ queries, and all SDK operations. The emulator intercepts requests in-process via FakeCosmosHandler.
Patterns 1–4 are one-liner Dependency Injection calls from ConfigureTestServices. Pattern 5 is for projects where Cosmos access is behind a custom factory interface — common in enterprise codebases with internal frameworks.
The most common pattern. Production code registers one CosmosClient and one or more Container instances.
// In Startup.cs / Program.cs
services.AddSingleton<CosmosClient>(sp =>
new CosmosClient(configuration["CosmosDb:ConnectionString"]));
services.AddSingleton<Container>(sp =>
{
var client = sp.GetRequiredService<CosmosClient>();
return client.GetContainer("MyDatabase", "orders");
});public class MyAppFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosDB();
});
}
}That's it! UseInMemoryCosmosDB() creates an InMemoryContainer and wires it through FakeCosmosHandler into a real CosmosClient. Your existing Container factory registrations are preserved — they resolve against the new CosmosClient, which routes all requests through the in-memory handler.
- The existing
CosmosClientregistration is removed - An
InMemoryContaineris created and wrapped inFakeCosmosHandler - A real
CosmosClientis created withConnectionMode.Gatewayand the handler intercepting all HTTP traffic - The
CosmosClientis registered as a singleton - Existing
Containerfactory registrations are kept — they callclient.GetContainer()on the real client, which routes throughFakeCosmosHandler
Note: The default in-memory container uses
/idas the partition key path. This works for most tests since production code passes explicitPartitionKeyvalues. If your tests rely on partition-key-path-based extraction (e.g.PartitionKey.None), use the explicit form below.
Use AddContainer() when you need to control the partition key path:
builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosDB(options => options
.AddContainer("orders", "/customerId"));
});- The existing
CosmosClientregistration is removed - The existing
Containerregistration(s) are removed -
InMemoryContainerinstances are created for eachAddContainer()call and wrapped inFakeCosmosHandler - A real
CosmosClientis registered (singleton), backed by the handler -
Containerinstances are registered (viaclient.GetContainer()), preserving the original lifetime
services.UseInMemoryCosmosDB(options => options
.AddContainer("orders", "/customerId")
.AddContainer("customers", "/id")
.AddContainer("products", "/categoryId"));Each container is registered as a separate Container service descriptor. If your production code resolves multiple Container instances, they'll each get a distinct in-memory container.
Important — explicit mode is all-or-nothing. As soon as you call
AddContainer(), all existingContainerregistrations are removed and only the containers you explicitly add are registered. If your production code registers three containers but you only callAddContainer()for one of them, the other two will not be available via Dependency Injection. Declare all containers your code depends on.
Use the Dependency Injection callbacks to seed data, register UDFs/stored procedures, or configure fault injection:
services.UseInMemoryCosmosDB(options =>
{
options.AddContainer("orders", "/customerId");
// Called after each FakeCosmosHandler is created
options.OnHandlerCreated = (containerName, handler) =>
{
// Seed data through the backing container
var backing = handler.BackingContainer;
backing.CreateItemAsync(
new { id = "seed-1", customerId = "c1", status = "active" },
new PartitionKey("c1")).Wait();
// Configure fault injection
handler.FaultInjector = req =>
{
if (req.Method == HttpMethod.Post)
return new HttpResponseMessage((HttpStatusCode)429);
return null;
};
};
// Called after the CosmosClient is created
options.OnClientCreated = client =>
{
// Access the real SDK CosmosClient for advanced scenarios
};
});See Seeding Data for more seeding strategies.
Wrap the HTTP handler with a DelegatingHandler for logging, tracking, or metrics:
services.UseInMemoryCosmosDB(options =>
{
options.AddContainer("orders", "/customerId");
options.WithHttpMessageHandlerWrapper(handler => new MyTrackingHandler(handler));
});- Integration/Component/Acceptance tests with
WebApplicationFactory - Any system that uses Dependency Injection for
CosmosClientorContainerregistrations - Code that calls
CosmosClient.GetContainer()directly - Default choice for most projects
Used when production code has multiple typed CosmosClient subclasses — each connecting to a different Cosmos DB account or database. Common in multi-tenant or domain-segmented architectures.
// Production typed clients
public class EmployeeCosmosClient : CosmosClient
{
public EmployeeCosmosClient(string connectionString) : base(connectionString) { }
}
public class CustomerCosmosClient : CosmosClient
{
public CustomerCosmosClient(string connectionString) : base(connectionString) { }
}
// Registration
services.AddSingleton(new EmployeeCosmosClient(config["Biometric:ConnectionString"]));
services.AddSingleton(new CustomerCosmosClient(config["OOB:ConnectionString"]));
// Repos resolve the typed client
public class BiometricRepository
{
private readonly EmployeeCosmosClient _client;
public BiometricRepository(EmployeeCosmosClient client) => _client = client;
}No shadow types needed. Pass your production typed client directly:
builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosDB<EmployeeCosmosClient>(options => options
.AddContainer("employee-data", "/employeeId"));
services.UseInMemoryCosmosDB<CustomerCosmosClient>(options => options
.AddContainer("customer-stuff", "/customerId"));
});- The existing
EmployeeCosmosClientregistration is removed - A Castle.Core dynamic proxy of
EmployeeCosmosClientis created, backed byFakeCosmosHandler - The proxy intercepts
GetContainer()calls to provide transparent hierarchical partition key prefix support - No
Containeror baseCosmosClientis registered — Pattern 2 repos callclient.GetContainer()directly - Repos that depend on
EmployeeCosmosClientresolve the proxied version
UseInMemoryCosmosDB<TClient>() uses Castle.Core (already a transitive dependency via NSubstitute) to create a runtime subclass of your production TClient. The proxy:
- Passes a fake connection string and
CosmosClientOptions(withFakeCosmosHandler) to the baseCosmosClientconstructor - Intercepts
GetContainer()to wrap the returnedContainerinPartitionKeyCapturingContainer— this is required for correct partition key handling with hierarchical prefix queries - Uses
GetContainerOnlyHookto restrict proxying to justGetContainer(), avoidingTypeLoadExceptionfromCosmosClient'sinternal virtualmethods - All other methods pass through to the real
CosmosClientbase class
-
TClientmust extendCosmosClient(directly or indirectly) -
TClientmust not besealed -
TClientmust have a public constructor accepting(string connectionString, CosmosClientOptions options)
| Limitation | Impact |
|---|---|
client.GetType() != typeof(TClient) |
The resolved instance is a Castle proxy subclass. client is TClient is true, but GetType() returns the proxy type. Avoid exact type checks in test assertions |
| Constructor side effects run with fake connection string | If your TClient constructor does more than call base(...), those side effects run with "AccountEndpoint=https://localhost:8081/;AccountKey=..."
|
| Debugger shows proxy type name | In the debugger, the instance appears as EmployeeCosmosClientProxy rather than EmployeeCosmosClient
|
| NativeAOT incompatible | Castle.Core uses Reflection.Emit — irrelevant for test code but worth noting |
Full SDK fidelity. Unlike previous versions (which used
InMemoryCosmosClient), Pattern 2 now goes through the real SDK HTTP pipeline viaFakeCosmosHandler. This means fault injection, query logging, LINQ.ToFeedIterator(), and serialization testing all work identically to Pattern 1.
Production code registers only CosmosClient, and each repository calls client.GetContainer() directly.
services.AddSingleton<CosmosClient>(sp =>
new CosmosClient(configuration["CosmosDb:ConnectionString"]));
public class OrderRepository
{
private readonly Container _container;
public OrderRepository(CosmosClient client)
{
_container = client.GetContainer("MyDb", "orders");
}
}builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosDB(options => options
.AddContainer("orders", "/customerId")
.AddContainer("customers", "/id"));
});When your repositories call client.GetContainer(), the real CosmosClient returns a standard Container proxy. All HTTP requests from that proxy are intercepted by FakeCosmosHandler, which routes them to the pre-configured InMemoryContainer with the correct partition key path. You must declare every container your repositories use via AddContainer().
Note: The SDK's LINQ-to-SQL translation is exercised, giving you higher fidelity than bypassing the SDK entirely. See Choosing Your Approach for a comparison of all approaches.
Use when you only want to replace Container registrations but keep the original CosmosClient (or don't have one registered).
New in 4.0:
UseInMemoryCosmosContainers()now usesFakeCosmosHandlerinternally — same full SDK fidelity asUseInMemoryCosmosDB().
services.AddSingleton<Container>(sp =>
{
var client = sp.GetRequiredService<CosmosClient>();
return client.GetContainer("MyDatabase", "orders");
});
public class OrderRepository(Container container)
{
public async Task<Order> GetOrder(string id, string partitionKey)
=> (await container.ReadItemAsync<Order>(id, new PartitionKey(partitionKey))).Resource;
}services.UseInMemoryCosmosContainers(options => options
.AddContainer("orders", "/customerId"));-
InMemoryContainerinstances are created and wrapped inFakeCosmosHandler - A hidden internal
CosmosClientis created withConnectionMode.Gatewayand the handler intercepting all HTTP traffic -
Containerinstances are registered in Dependency Injection (viaclient.GetContainer()), preserving the original lifetime -
CosmosClientis NOT touched — any existing registration remains as-is
services.UseInMemoryCosmosContainers(options =>
{
options.AddContainer("orders", "/customerId");
options.DatabaseName = "TestDb";
// Access the backing handler for fault injection or logging
options.OnHandlerCreated = (name, handler) =>
{
handler.FaultInjector = req => /* simulate 429s, 503s, etc. */;
};
// Access the backing container for seeding and test setup
options.OnContainerCreated = setup =>
{
// Seed data, register UDFs/triggers, etc.
};
// Wrap the HTTP handler (e.g. for logging)
options.WithHttpMessageHandlerWrapper(h => new MyLoggingHandler(h));
});- Repositories depend on
Containerdirectly (injected via Dependency Injection), notCosmosClient - The
CosmosClientis either not registered or is used only as a factory during startup - You want to keep the production
CosmosClientregistration for other purposes
UseInMemoryCosmosContainers() does NOT replace the production CosmosClient. This means:
-
If production code uses
CosmosClient.GetContainer(), those containers still point to the production endpoint — only containers resolved directly viaIServiceProvider.GetRequiredService<Container>()are replaced. UseUseInMemoryCosmosDB()instead for that scenario. -
The hidden internal
CosmosClientis not accessible via Dependency Injection — useUseInMemoryCosmosDB()withOnClientCreatedif you need aCosmosClientreference
Used when production code resolves containers through a custom factory interface (e.g. IDatabaseClientFactory) rather than registering CosmosClient or Container directly in Dependency Injection. Common in enterprise codebases with internal frameworks. Uses InMemoryCosmos.Builder() (or InMemoryCosmos.Create() for single-container).
public class CosmosDbFixture : IDatabaseClientFactory, IDisposable
{
private readonly InMemoryCosmosResult _cosmos;
public CosmosDbFixture()
{
_cosmos = InMemoryCosmos.Builder()
.AddContainer("transactions", "/merchantId")
.AddContainer("idempotency-locks", "/endpoint")
.AddContainer("orders", "/customerId")
.AddContainer("audit-log", new[] { "/tenantId", "/category", "/date" })
.Build();
}
public Container GetContainer(string containerName)
=> _cosmos.Containers[containerName];
/// <summary>Direct access to test setup for registering sprocs, UDFs, triggers.</summary>
public IContainerTestSetup SetupContainer(string containerName)
=> _cosmos.SetupContainer(containerName);
/// <summary>Import state for seeding test data.</summary>
public void ImportStateFromFile(string containerName, string filePath)
=> _cosmos.ImportStateFromFile(filePath, containerName);
public void Dispose() => _cosmos.Dispose();
}
⚠️ Serialization: camelCaseidrequired. When usingFakeCosmosHandler, the Cosmos SDK serializes your C# objects to JSON before sending them over HTTP. If your models use PascalCaseId, the JSON will contain"Id"instead of"id", and upserts/creates will fail with "Item must have an 'id' property". Fix this by configuring the serializer viaConfigureOptions():_cosmos = InMemoryCosmos.Builder() .AddContainer("transactions", "/merchantId") .ConfigureOptions(opts => opts.Serializer = new CosmosJsonDotNetSerializer( new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() })) .Build();For Patterns 1–4 (the Dependency Injection extension methods), the
CosmosClientis created internally with the SDK's default serializer — ensure your models use lowercaseidvia attributes (e.g.[JsonProperty("id")]) or naming conventions. See Troubleshooting for more common issues.
Register in test Dependency Injection:
builder.ConfigureTestServices(services =>
{
var fixture = new CosmosDbFixture();
services.AddSingleton<IDatabaseClientFactory>(fixture);
});-
InMemoryCosmos.Builder()createsInMemoryContainerinstances, wraps each in aFakeCosmosHandler, and builds a realCosmosClientwithConnectionMode.Gateway— all internally - The SDK's full pipeline executes (LINQ → SQL translation, partition key routing, continuation tokens)
-
.ToFeedIterator()works unchanged — the SDK translates LINQ to SQL, sends it over HTTP, and the handler executes it via the SQL query engine - SQL queries, CRUD, patches, batches — everything goes through the real SDK pipeline
Seed data through the Container returned by the fixture, or use SetupContainer() for direct access:
var container = fixture.GetContainer("transactions");
await container.CreateItemAsync(
new Transaction { Id = "tx-1", MerchantId = "m-1" },
new PartitionKey("m-1"));See Seeding Data for more seeding strategies.
- Production code resolves containers through a custom factory interface (e.g.
IDatabaseClientFactory) - Cosmos access is abstracted behind an internal framework rather than using
CosmosClientorContainerdirectly in Dependency Injection
- Containers must be pre-declared via
AddContainer()(not auto-created on demand) - Requires a fixture class that implements your factory interface — slightly more setup than Patterns 1–4
Related: For test sequence diagram tracking with Pattern 5, see the TestTrackingDiagrams CosmosDB Extension integration guide. It shows how to wrap the handler with a tracking handler via
WrapHandler()to capture Cosmos operations in your test diagrams.
No Dependency Injection required. The simplest way to get full SDK fidelity in unit/component tests. Handles all FakeCosmosHandler wiring internally. For detailed usage examples (CRUD, SQL queries, LINQ, patches, test isolation), see Unit Testing.
using var cosmos = InMemoryCosmos.Create("orders", "/customerId");
// Real SDK Container — all calls go through FakeCosmosHandler
var container = cosmos.Container;
await container.CreateItemAsync(order, new PartitionKey(order.CustomerId));
// Test setup
cosmos.SetupContainer().RegisterUdf("discount", args => (double)args[0] * 0.9);
// Fault injection
cosmos.Handler.FaultInjector = req => new HttpResponseMessage((HttpStatusCode)429);using var cosmos = InMemoryCosmos.Builder()
.AddContainer("orders", "/customerId")
.AddContainer("products", "/categoryId")
.WrapHandler(h => new MyTrackingHandler(h)) // Only needed if you want to wrap the HTTP handler
.ConfigureOptions(opts => opts.Serializer = mySerializer) // Only needed if you use a custom serializer
.Build();
var client = cosmos.Client;
cosmos.SetupContainer("orders").RegisterStoredProcedure("myProc", (pk, args) => "result");
cosmos.GetHandler("orders").FaultInjector = req => ...;
cosmos.SetFaultInjector(req => ...); // all containersSee Multi-Database for AddDatabase() with overlapping container names.
using var cosmos = InMemoryCosmos.Create("orders",
new[] { "/tenantId", "/customerId" });services.UseInMemoryCosmosDB(options =>
{
// Add containers with custom partition key paths
options.AddContainer("orders", "/customerId");
options.AddContainer("customers", "/id", databaseName: "customer-db");
// Override the default database name (defaults to "in-memory-db")
options.DatabaseName = "my-database";
// Callback after client creation (e.g. capture reference)
options.OnClientCreated = client =>
{
// client is the original CosmosClient backed by FakeCosmosHandler
};
// Callback for each FakeCosmosHandler after creation (e.g. seed data, configure fault injection)
options.OnHandlerCreated = (containerName, handler) =>
{
var backingContainer = handler.BackingContainer;
backingContainer.CreateItemAsync(new { id = "seed-1", customerId = "c1" },
new PartitionKey("c1")).Wait();
};
// Wrap the FakeCosmosHandler with a custom DelegatingHandler (e.g. for logging, tracking).
// The function receives the handler/router and must return the outermost handler.
// Added in v2.0.5.
options.WithHttpMessageHandlerWrapper(fakeHandler =>
new MyLoggingHandler(fakeHandler));
});services.UseInMemoryCosmosContainers(options =>
{
options.AddContainer("orders", "/customerId");
// Callback for each container after creation
options.OnContainerCreated = container =>
{
container.DefaultTimeToLive = 3600; // 1 hour TTL
};
});public record ContainerConfig(
string ContainerName,
string PartitionKeyPath = "/id",
string? DatabaseName = null);See the API Reference for full property details on all classes.
All three Dependency Injection methods preserve the lifetime of existing registrations:
- If production registered
Containeras scoped, the in-memory replacement is also scoped - If production registered
CosmosClientas singleton, the in-memory replacement is also singleton - If no existing registration is found, singleton is used as the default
This means your test Dependency Injection graph has the same lifetime characteristics as production.
public class OrderApiTests : IClassFixture<OrderApiFactory>
{
private readonly HttpClient _client;
public OrderApiTests(OrderApiFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task GetOrder_ReturnsOrder()
{
// Arrange — seed via API or direct container access
var response = await _client.PostAsJsonAsync("/api/orders",
new { customerId = "cust-1", product = "Widget" });
response.EnsureSuccessStatusCode();
// Act
var order = await _client.GetFromJsonAsync<Order>("/api/orders/cust-1");
// Assert
order.Should().NotBeNull();
order!.Product.Should().Be("Widget");
}
}
public class OrderApiFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.UseInMemoryCosmosDB(options => options
.AddContainer("orders", "/customerId"));
});
}
}For simpler unit tests that don't need Dependency Injection or WebApplicationFactory, see Unit Testing.
Your production code uses .ToFeedIterator() which requires the real Cosmos SDK LINQ provider. All recommended Dependency Injection patterns (UseInMemoryCosmosDB(), UseInMemoryCosmosContainers()) use FakeCosmosHandler internally, so .ToFeedIterator() works unchanged.
If you see this error, you're likely using an internal class directly. Migrate to one of the recommended patterns — see How Does ToFeedIterator() Work? for details.
Ensure the container names and partition key paths in your test setup match what production code expects:
// If production does: client.GetContainer("MyDb", "orders")
// Then test setup needs:
options.AddContainer("orders", "/customerId"); // container name must matchEach AddContainer() call creates a separate InMemoryContainer instance. If your production code resolves Container from Dependency Injection, it gets the first one registered. If you need multiple containers, consider Pattern 3 (repos call client.GetContainer()) instead of resolving multiple Container instances from Dependency Injection.
For more troubleshooting tips, see the Troubleshooting page.
- Choosing Your Approach — comparison table and decision flowchart
- Getting Started — installation and quick start
- Seeding Data — all seeding strategies (Dependency Injection callbacks, SDK methods, file/snapshot)
- State Persistence — export/import and auto-persistence
- Unit Testing — simpler unit tests without Dependency Injection
- API Reference — full property details for all configuration classes
- Known Limitations — current limitations and workarounds
Getting Started
Integration & Dependency Injection
Data Management
Reference
Help