Skip to content

How Does ToFeedIterator Work

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

.ToFeedIterator() is a Cosmos SDK extension method that converts a LINQ queryable into a paginated FeedIterator<T>. In production, it relies on the SDK's internal CosmosLinqQueryProvider to translate LINQ into Cosmos SQL and execute it over HTTP. This page explains how FakeCosmosHandler makes .ToFeedIterator() work in tests without any production code changes.

TL;DR: All recommended integration methods (UseInMemoryCosmosDB(), UseInMemoryCosmosContainers(), InMemoryCosmos.Create()) handle this automatically. If .ToFeedIterator() works in your production code, it works in tests — no changes needed.


The Problem

The Cosmos SDK's .ToFeedIterator() only works on queryables created by the SDK's own LINQ provider (CosmosLinqQueryProvider). If you were to use a raw InMemoryContainer directly, its GetItemLinqQueryable<T>() returns a standard LINQ-to-Objects queryable (EnumerableQuery<T>). Calling .ToFeedIterator() on that throws:

ArgumentOutOfRangeException: ToFeedIterator is only supported on Cosmos LINQ query operations

FakeCosmosHandler solves this entirely — read on to see how.


How FakeCosmosHandler Makes It Work

When you use UseInMemoryCosmosDB(), UseInMemoryCosmosContainers(), or InMemoryCosmos.Create(), a real CosmosClient is created with FakeCosmosHandler intercepting all HTTP traffic. The Container object returned by client.GetContainer() is a real SDK Container — not a fake. This means:

  1. GetItemLinqQueryable<T>() returns a queryable backed by the SDK's real CosmosLinqQueryProvider
  2. .ToFeedIterator() works because it's called on the real SDK LINQ provider — there's no fake to break it
  3. The SDK translates your LINQ expression tree into Cosmos SQL internally
  4. The SDK serializes the SQL as an HTTP POST to the Cosmos endpoint
  5. FakeCosmosHandler intercepts the HTTP request before it leaves the process
  6. The handler parses the SQL (via CosmosSqlParser), executes it against the backing InMemoryContainer, and returns paged JSON responses with continuation tokens

The interception happens at the HTTP level, bound to the CosmosClient instance. There is no ambient state, no AsyncLocal, and no ExecutionContext dependency.

What This Means for Test Fidelity

Because the SDK's LINQ-to-SQL translation is exercised, your tests will catch LINQ expressions that generate invalid Cosmos SQL. If the SDK can't translate a LINQ expression, you'll find out during testing rather than in production.

Example

// Test setup (DI)
services.UseInMemoryCosmosDB(o => o.AddContainer("orders", "/customerId"));

// Or test setup (non-DI)
using var cosmos = InMemoryCosmos.Create("orders", "/customerId");
var container = cosmos.Container;

// Production code — completely unchanged
var iterator = container
    .GetItemLinqQueryable<Order>()
    .Where(o => o.Status == "active")
    .ToFeedIterator();  // ← works! Real SDK LINQ provider

while (iterator.HasMoreResults)
{
    var response = await iterator.ReadNextAsync();
    // process response
}

Under the Hood: What FakeCosmosHandler Does with Queries

When the SDK calls .ToFeedIterator(), the LINQ expression tree is translated to SQL internally, then sent as an HTTP POST to /docs with the SQL in the request body. FakeCosmosHandler.HandleQueryAsync then:

  1. Reads the SQL from the POST body
  2. Extracts partition key, max item count, continuation token, and range ID from HTTP headers
  3. Checks the query result cache (for pagination continuations)
  4. Parses via CosmosSqlParser.TryParse() (see SQL Queries for supported syntax):
    • ORDER BY queries: Detected structurally (the SDK wraps ORDER BY in a special [{"item": <expr>}] array format). Handled by HandleOrderByQueryAsync which re-wraps results with orderByItems and payload aliases.
    • Regular queries: Simplified via CosmosSqlParser.SimplifySdkQuery() to strip SDK-internal decorations, then delegated to InMemoryContainer.GetItemQueryIterator<JToken>()
  5. Filters by partition key range (for multi-range fan-out testing)
  6. Builds a paged HTTP response via BuildPagedResponse with x-ms-continuation headers

Legacy Approach (Deprecated)

Deprecated. This approach is no longer needed. All recommended integration methods now use FakeCosmosHandler, which handles .ToFeedIterator() natively. The CosmosDB.InMemoryEmulator.ProductionExtensions package is deprecated.

The legacy approach was designed for tests that used raw InMemoryContainer directly (not via FakeCosmosHandler). It required:

  1. Adding the CosmosDB.InMemoryEmulator.ProductionExtensions NuGet package to production code
  2. Changing every .ToFeedIterator() call to use the legacy extension method
  3. Calling InMemoryFeedIteratorSetup.Register() in test setup

This approach still works for backward compatibility but is no longer recommended. If you're using it, consider migrating to UseInMemoryCosmosDB() or InMemoryCosmos.Create() — both handle .ToFeedIterator() natively with zero production code changes.


See Also

Clone this wiki locally