Skip to content

How It Works

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

All recommended approaches use FakeCosmosHandler under the hood. Here's how it compares to other in-memory implementations:

Dimension Our Approach: FakeCosmosHandler Other In-Memory Implementations
Wiring CosmosClient (SDK)
     ↓ GetContainer()
Container (SDK)
     ↓ HTTP
FakeCosmosHandler
     ↓
InMemoryContainer
Fake CosmosClient / fake Container subclasses — operations execute directly against in-memory storage without involving the SDK
How it works A real CosmosClient with an intercepted HTTP layer — client.GetContainer() returns a real SDK Container, which serializes requests and builds HTTP messages exactly as in production; FakeCosmosHandler (a custom HttpMessageHandler) intercepts them before they reach the network and routes them to an InMemoryContainer Fake subclasses override SDK methods directly — your code calls their methods; all operations execute against in-memory storage without involving the SDK HTTP pipeline
Production code changes None May require production changes (e.g. for LINQ support)
Serialization Your configured CosmosSerializer / CosmosClientOptions is exercised — serialization mismatches are caught Usually Built-in serializer — any CosmosSerializer on CosmosClientOptions is not exercised
SQL queries SDK builds an HTTP query request → FakeCosmosHandler extracts the SQL → CosmosSqlParser executes it Usually N/A
LINQ queries SDK translates LINQ → Cosmos SQL → CosmosSqlParser executes it¹ LINQ-to-Objects — .Where() and .OrderBy() run as C# delegates¹
SDK fidelity Highest — real SDK serialization, HTTP request construction, query plan generation, partition key range discovery, and response deserialization all execute; only the network call is intercepted Lower — bypasses SDK entirely; no HTTP request/response cycle, no SDK serialization, no retry logic
Multi-container InMemoryCosmos.Builder() or DI AddContainer() — dispatches by container name Varies by implementation
Fault injection Yes (429s, 503s, timeouts) No — there's no HTTP layer to inject faults into
Query / request logging Yes No
Pagination SDK-compatible continuation tokens (opaque, same format as production) Offset-based (integer continuation tokens)
Setup complexity Minimal Minimal

All approaches support CRUD operations (create, read, upsert, replace, delete, patch) and SQL queries via CosmosSqlParser. All run entirely in-process — nothing goes over the network.

¹ Why LINQ execution differs: With fake subclasses, GetItemLinqQueryable<T>() returns a LINQ-to-Objects queryable — .Where() and .OrderBy() execute as C# delegates directly against in-memory data. With FakeCosmosHandler, the real SDK's CosmosLinqQueryProvider translates your LINQ expression tree into Cosmos DB SQL, and the handler executes that SQL through CosmosSqlParser. This means FakeCosmosHandler is the only approach that tests the SDK's LINQ-to-SQL translation — if the SDK can't translate a LINQ expression, you'll find out during testing rather than in production.


FakeCosmosHandler — Internals

FakeCosmosHandler is the core of this library. It's a custom HttpMessageHandler injected into a real CosmosClient. The SDK builds and serializes HTTP requests exactly as in production; FakeCosmosHandler intercepts them before they leave the process and serves responses from the in-memory store. Your production code stays completely untouched — including .ToFeedIterator() and all LINQ operations.

You never need to wire FakeCosmosHandler directly — InMemoryCosmos.Create(), InMemoryCosmos.Builder(), UseInMemoryCosmosDB(), and UseInMemoryCosmosContainers() all handle this automatically. The details below explain what's happening under the hood.

Fault Injection

Simulate transient failures, throttling, and timeouts:

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

cosmos.Handler.FaultInjector = request =>
{
    // Simulate 429 throttling on write operations
    if (request.Method == HttpMethod.Post)
    {
        return new HttpResponseMessage((HttpStatusCode)429)
        {
            Headers = { RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromMilliseconds(1)) }
        };
    }
    return null; // No fault — proceed normally
};

// Don't inject faults on SDK metadata requests
cosmos.Handler.FaultInjectorIncludesMetadata = false;

For multi-container setups, inject faults per container or globally:

using var cosmos = InMemoryCosmos.Builder()
    .AddContainer("orders", "/customerId")
    .AddContainer("products", "/categoryId")
    .Build();

// Per-container fault injection
cosmos.GetHandler("orders").FaultInjector = req => ...;

// All containers at once
cosmos.SetFaultInjector(req => ...);

SDK Compatibility Verification

Detect breaking changes in the Cosmos SDK before they cause suprising failing tests:

// Call once during test suite setup (e.g. in a collection fixture)
await FakeCosmosHandler.VerifySdkCompatibilityAsync();

Query and Request Logging

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

// ... run tests ...

// After running tests
foreach (var request in cosmos.Handler.RequestLog)
    Console.WriteLine(request);  // "POST /dbs/db/colls/orders/docs"

foreach (var query in cosmos.Handler.QueryLog)
    Console.WriteLine(query);    // "SELECT * FROM c WHERE c.status = 'active'"

Hierarchical Partition Key Prefix Queries

When querying with a prefix of a hierarchical (composite) partition key — e.g. querying by tenantId only on a container with PK paths ["/tenantId", "/category", "/date"] — the Cosmos SDK strips the prefix key at the transport layer and routes by partition key range ID instead. This means FakeCosmosHandler never sees the partition key in the HTTP request.

InMemoryCosmos handles this transparently by wrapping the Container with a decorator that captures the partition key from your query methods and forwards it to the handler via AsyncLocal. This works for both SQL queries (GetItemQueryIterator) and LINQ queries (.GetItemLinqQueryable().ToFeedIterator()).

using var cosmos = InMemoryCosmos.Create("events",
    new[] { "/tenantId", "/category", "/date" });

var container = cosmos.Container;

// Query with PK prefix — just works
var prefixPk = new PartitionKeyBuilder().Add("tenant-1").Build();
var iterator = container.GetItemQueryIterator<Event>(
    "SELECT * FROM c",
    requestOptions: new QueryRequestOptions { PartitionKey = prefixPk });

Strengths

  • ✅ Highest fidelity — real SDK serialization, HTTP request construction, query plan generation, partition key range discovery, and response deserialization all execute; only the network call is intercepted
  • ✅ Your configured CosmosSerializer / CosmosClientOptions is exercised — serialization mismatches are caught
  • ✅ LINQ-to-SQL translation tested — the SDK translates your LINQ into Cosmos SQL, which CosmosSqlParser then executes
  • ✅ Fault injection for testing retry/throttle handling
  • ✅ Query and request logging
  • ✅ SDK-compatible continuation tokens (opaque, same format as production)
  • ✅ Multi-container routing
  • ✅ Custom DelegatingHandler wrapping via WrapHandler() on the builder
  • ✅ Queries with a hierarchical partition key prefix work transparently

Limitations

  • Requires ConnectionMode.Gateway (Cosmos's direct mode bypasses HttpMessageHandler)

Clone this wiki locally