-
Notifications
You must be signed in to change notification settings - Fork 2
Migration Guide
v4.0 unifies all integration approaches behind FakeCosmosHandler. The main change is that UseInMemoryCosmosContainers() now creates a hidden internal CosmosClient backed by FakeCosmosHandler — matching how UseInMemoryCosmosDB() has always worked. InMemoryCosmosClient is deprecated, and the ProductionExtensions package is no longer needed.
If your code only uses UseInMemoryCosmosDB() or InMemoryCosmos.Create() / InMemoryCosmos.Builder(): No changes required. v4.0 is fully backward compatible for these patterns.
If your code uses any of the following, read the relevant section below:
UseInMemoryCosmosContainers()new InMemoryCosmosClient()UseInMemoryCosmosDB<TClient>()-
ProductionExtensionspackage (deprecated) RegisterFeedIteratorSetup
Impact: Containers resolved from DI are now real SDK Container objects (not InMemoryContainer). This is the only truly breaking change in 4.0.
What breaks:
- Code that casts
ContainertoInMemoryContainerwill throwInvalidCastException - Code that accessed
InMemoryContainer-specific members (e.g.ClearItems(),RegisterUdf()) on the DI-resolved container
Before (3.x):
services.UseInMemoryCosmosContainers(o => o.AddContainer("orders", "/pk"));
// Later in test setup or assertions:
var container = provider.GetRequiredService<Container>();
var inMemory = (InMemoryContainer)container; // ← worked in 3.x
inMemory.ClearItems();
inMemory.RegisterUdf("discount", args => (double)args[0] * 0.9);After (4.0):
InMemoryContainer? backing = null;
FakeCosmosHandler? handler = null;
services.UseInMemoryCosmosContainers(o =>
{
o.AddContainer("orders", "/pk");
// Access the backing InMemoryContainer
o.OnContainerCreated = c => backing = c;
// Or access via handler.BackingContainer
o.OnHandlerCreated = (name, h) => handler = h;
});
// Later:
backing!.ClearItems();
backing!.RegisterUdf("discount", args => (double)args[0] * 0.9);
// Or via handler:
handler!.BackingContainer.ClearItems();What you gain: The Container from DI is now a real SDK Container with full SDK fidelity — .ToFeedIterator() works natively, your CosmosSerializer is exercised, LINQ is translated to SQL, and fault injection is available.
Impact: Compilation warnings. With TreatWarningsAsErrors=true, these become build errors.
Quick fix (to unblock immediately): Add <NoWarn>$(NoWarn);CS0618</NoWarn> to your test project's .csproj:
<PropertyGroup>
<NoWarn>$(NoWarn);CS0618</NoWarn>
</PropertyGroup>Proper migration: Replace new InMemoryCosmosClient() with InMemoryCosmos.Create() or InMemoryCosmos.Builder():
Before (3.x) — single container:
var client = new InMemoryCosmosClient();
var container = client.GetContainer("db", "orders");
await container.CreateItemAsync(item, new PartitionKey(item.Pk));After (4.0) — single container:
using var cosmos = InMemoryCosmos.Create("orders", "/pk");
await cosmos.Container.CreateItemAsync(item, new PartitionKey(item.Pk));Before (3.x) — multiple containers:
var client = new InMemoryCosmosClient();
var orders = client.GetContainer("db", "orders");
var products = client.GetContainer("db", "products");After (4.0) — multiple containers:
using var cosmos = InMemoryCosmos.Builder()
.AddContainer("orders", "/customerId")
.AddContainer("products", "/categoryId")
.Build();
var orders = cosmos.Client.GetContainer("default", "orders");
var products = cosmos.Client.GetContainer("default", "products");Before (3.x) — custom factory pattern:
public class InMemoryDatabaseClientFactory : IDatabaseClientFactory
{
private readonly InMemoryCosmosClient _client = new();
public Container GetContainer(string name) => _client.GetContainer("db", name);
}After (4.0) — custom factory with FakeCosmosHandler:
public class InMemoryDatabaseClientFactory : IDatabaseClientFactory, IDisposable
{
private readonly InMemoryCosmos _cosmos;
public InMemoryDatabaseClientFactory(params (string name, string pkPath)[] containers)
{
var builder = InMemoryCosmos.Builder();
foreach (var (name, pkPath) in containers)
builder.AddContainer(name, pkPath);
_cosmos = builder.Build();
}
public Container GetContainer(string name) => _cosmos.Client.GetContainer("default", name);
public void Dispose() => _cosmos.Dispose();
}Or use Pattern 5 (Custom Factory) from the Setup Guide for the recommended approach.
Breaking change in 4.0.3: The generic constraint changed from TClient : InMemoryCosmosClient, new() to TClient : CosmosClient. Shadow types are no longer needed — pass your production typed client directly.
Before (3.x / 4.0.0–4.0.2):
// Test project shadow type (no longer needed)
public class EmployeeCosmosClient : InMemoryCosmosClient { }
// In ConfigureTestServices:
services.UseInMemoryCosmosDB<EmployeeCosmosClient>(...);After (4.0.3+):
// Use your PRODUCTION type directly — no shadow types:
services.UseInMemoryCosmosDB<EmployeeCosmosClient>(...);The generic method now uses FakeCosmosHandler internally (same as the non-generic method), providing full SDK fidelity: fault injection, query logging, LINQ .ToFeedIterator(), and serialization testing. A Castle.Core dynamic proxy intercepts GetContainer() for transparent partition key handling. See Setup Guide — Pattern 2 for details and limitations.
Impact: If you were using the ProductionExtensions package specifically for UseInMemoryCosmosContainers() scenarios, the production code change is no longer necessary.
Before (3.x):
// Production code (required change):
var iterator = container.GetItemLinqQueryable<Order>()
.Where(o => o.Status == "active")
.ToFeedIterator(); // ← didn't work with UseInMemoryCosmosContainers() — needed ProductionExtensions workaround
// Test setup:
InMemoryFeedIteratorSetup.Register();After (4.0):
// Production code (revert to standard SDK):
var iterator = container.GetItemLinqQueryable<Order>()
.Where(o => o.Status == "active")
.ToFeedIterator(); // ← works natively now
// Test setup: nothing extra neededSteps:
- Replace the legacy feed iterator extension with
.ToFeedIterator()in production code - Remove
InMemoryFeedIteratorSetup.Register()from test setup - Remove the
CosmosDB.InMemoryEmulator.ProductionExtensionsNuGet from your production project
Note: If you still use raw
InMemoryContainerdirectly (now internal), the legacy extension is still needed for those cases. But for all Dependency Injection andInMemoryCosmospaths, it's unnecessary.
Impact: Compilation warnings. The property is ignored — UseInMemoryCosmosContainers() no longer calls InMemoryFeedIteratorSetup.Register() since FakeCosmosHandler handles .ToFeedIterator() natively.
Fix: Remove the property from your options:
// Before:
services.UseInMemoryCosmosContainers(o =>
{
o.AddContainer("orders", "/pk");
o.RegisterFeedIteratorSetup = false; // ← remove this
});
// After:
services.UseInMemoryCosmosContainers(o =>
{
o.AddContainer("orders", "/pk");
});InMemoryContainerOptions now supports properties previously only available on InMemoryCosmosOptions:
| Property | Type | Purpose |
|---|---|---|
DatabaseName |
string |
Database name for the hidden internal CosmosClient (default: "in-memory-db") |
OnHandlerCreated |
Action<string, FakeCosmosHandler> |
Callback for each FakeCosmosHandler after creation — configure fault injection, access request/query logs |
WithHttpMessageHandlerWrapper() |
Func<HttpMessageHandler, HttpMessageHandler> |
Wrap the HTTP handler (e.g. for logging, tracking, metrics) |
| If you use... | Action needed |
|---|---|
UseInMemoryCosmosDB() |
None |
InMemoryCosmos.Create() / .Builder()
|
None |
UseInMemoryCosmosContainers() |
Check for (InMemoryContainer) casts — use OnContainerCreated/OnHandlerCreated instead |
new InMemoryCosmosClient() |
Replace with InMemoryCosmos.Create() or suppress CS0618
|
UseInMemoryCosmosDB<TClient>() |
4.0.3+: Remove shadow types — pass production typed client directly. Constraint changed to TClient : CosmosClient
|
ProductionExtensions package |
Replace the legacy feed iterator extension with .ToFeedIterator()
|
RegisterFeedIteratorSetup |
Remove the property |
ProductionExtensions NuGet |
Remove from production project |
These FakeCosmosHandler methods are no longer part of the public API. Use InMemoryCosmos instead.
Before (2.x):
var container = new InMemoryContainer("orders", "/customerId");
var handler = new FakeCosmosHandler(container);
// ❌ No longer available
var client = handler.CreateClient();After (3.0.0):
// ✅ Single container
using var cosmos = InMemoryCosmos.Create("orders", "/customerId");
var client = cosmos.Client;
var container = cosmos.Container;Before (2.x):
var ordersContainer = new InMemoryContainer("orders", "/customerId");
var ordersHandler = new FakeCosmosHandler(ordersContainer);
var productsContainer = new InMemoryContainer("products", "/categoryId");
var productsHandler = new FakeCosmosHandler(productsContainer);
// ❌ No longer available
var router = FakeCosmosHandler.CreateRouter(new Dictionary<string, FakeCosmosHandler>
{
["orders"] = ordersHandler,
["products"] = productsHandler
});
var options = new CosmosClientOptions
{
ConnectionMode = ConnectionMode.Gateway,
HttpClientFactory = () => new HttpClient(router)
};
var innerClient = new CosmosClient(connectionString, options);
// ❌ No longer available
var client = FakeCosmosHandler.WrapClient(innerClient, handlers);After (3.0.0):
// ✅ Builder handles all wiring
using var cosmos = InMemoryCosmos.Builder()
.AddContainer("orders", "/customerId")
.AddContainer("products", "/categoryId")
.Build();
var client = cosmos.Client;
var orders = cosmos.Containers["orders"];
var products = cosmos.Containers["products"];Before (2.x):
var router = FakeCosmosHandler.CreateRouter(handlers);
var client = new CosmosClient(connectionString, new CosmosClientOptions
{
ConnectionMode = ConnectionMode.Gateway,
Serializer = myCustomSerializer,
HttpClientFactory = () => new HttpClient(router)
});
var wrappedClient = FakeCosmosHandler.WrapClient(client, handlers);After (3.0.0):
using var cosmos = InMemoryCosmos.Builder()
.AddContainer("orders", "/customerId")
.ConfigureOptions(opts => opts.Serializer = myCustomSerializer)
.Build();Before (2.x):
handler.FaultInjector = req => new HttpResponseMessage((HttpStatusCode)429);After (3.0.0):
// Single container
cosmos.Handler.FaultInjector = req => new HttpResponseMessage((HttpStatusCode)429);
// Named container
cosmos.GetHandler("orders").FaultInjector = ...;
// All containers
cosmos.SetFaultInjector(req => new HttpResponseMessage((HttpStatusCode)429));Before (2.x):
container.RegisterUdf("myUdf", args => (double)args[0] * 2);After (3.0.0):
// Via InMemoryCosmos
cosmos.SetupContainer().RegisterUdf("myUdf", args => (double)args[0] * 2);
// Or seed during creation
using var cosmos = InMemoryCosmos.Create("orders", "/pk",
configureContainer: c => c.RegisterUdf("myUdf", args => (double)args[0] * 2));using var cosmos = InMemoryCosmos.Create("orders", "/customerId");using var cosmos = InMemoryCosmos.Builder()
.AddContainer("orders", "/customerId")
.AddContainer("products", "/categoryId")
.WrapHandler(h => new MyLoggingHandler(h))
.ConfigureOptions(opts => opts.Serializer = mySerializer)
.Build();using var cosmos = InMemoryCosmos.Builder()
.AddDatabase("users-db", db => db.AddContainer("events", "/userId"))
.AddDatabase("orders-db", db => db.AddContainer("events", "/orderId"))
.Build();
var userEvents = cosmos.Database("users-db").Containers["events"];A focused interface exposing only test-relevant operations:
-
RegisterStoredProcedure(),RegisterUdf(),RegisterTrigger() -
ExportState(),ImportState(),ClearItems()
Production startup code that calls CreateContainerAsync, DeleteContainerAsync, etc. now works without pre-registration.
-
DI integration:
UseInMemoryCosmosDB()works exactly as before -
InMemoryContainer: Still available for direct usage in unit tests (deprecated in 4.0 — useInMemoryCosmos.Create()) -
InMemoryCosmosClient: Still available (deprecated in 4.0 — useInMemoryCosmos.Create()) -
FakeCosmosHandler: Still the underlying HTTP handler — just accessed viacosmos.Handlerinstead of constructed directly - All query, CRUD, and feature behavior: No changes to emulation fidelity
Getting Started
Integration & Dependency Injection
Data Management
Reference
Help