Skip to content

Migration Guide

Aryeh Citron edited this page Apr 20, 2026 · 8 revisions

3.x → 4.0.0

Overview

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>()
  • ProductionExtensions package (deprecated)
  • RegisterFeedIteratorSetup

UseInMemoryCosmosContainers() now uses FakeCosmosHandler

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 Container to InMemoryContainer will throw InvalidCastException
  • 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.


InMemoryCosmosClient is [Obsolete]

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.


UseInMemoryCosmosDB<TClient>() (Typed Client Pattern)

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.


ProductionExtensions package no longer needed

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 needed

Steps:

  1. Replace the legacy feed iterator extension with .ToFeedIterator() in production code
  2. Remove InMemoryFeedIteratorSetup.Register() from test setup
  3. Remove the CosmosDB.InMemoryEmulator.ProductionExtensions NuGet from your production project

Note: If you still use raw InMemoryContainer directly (now internal), the legacy extension is still needed for those cases. But for all Dependency Injection and InMemoryCosmos paths, it's unnecessary.


RegisterFeedIteratorSetup on InMemoryContainerOptions is [Obsolete]

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");
});

New InMemoryContainerOptions properties

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)

Quick Reference: What Changed

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

2.x → 3.0.0

Breaking Changes

CreateClient(), WrapClient(), CreateRouter() are now internal

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;

Multi-container migration

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"];

Custom CosmosClientOptions

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();

Fault injection

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));

Test setup (UDFs, stored procedures, triggers)

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));

New Features in 3.0.0

InMemoryCosmos.Create() — single-container one-liner

using var cosmos = InMemoryCosmos.Create("orders", "/customerId");

InMemoryCosmos.Builder() — multi-container

using var cosmos = InMemoryCosmos.Builder()
    .AddContainer("orders", "/customerId")
    .AddContainer("products", "/categoryId")
    .WrapHandler(h => new MyLoggingHandler(h))
    .ConfigureOptions(opts => opts.Serializer = mySerializer)
    .Build();

Multi-database support

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"];

IContainerTestSetup interface

A focused interface exposing only test-relevant operations:

  • RegisterStoredProcedure(), RegisterUdf(), RegisterTrigger()
  • ExportState(), ImportState(), ClearItems()

Dynamic container management

Production startup code that calls CreateContainerAsync, DeleteContainerAsync, etc. now works without pre-registration.

What's Unchanged (2.x → 3.0)

  • DI integration: UseInMemoryCosmosDB() works exactly as before
  • InMemoryContainer: Still available for direct usage in unit tests (deprecated in 4.0 — use InMemoryCosmos.Create())
  • InMemoryCosmosClient: Still available (deprecated in 4.0 — use InMemoryCosmos.Create())
  • FakeCosmosHandler: Still the underlying HTTP handler — just accessed via cosmos.Handler instead of constructed directly
  • All query, CRUD, and feature behavior: No changes to emulation fidelity

Clone this wiki locally