-
Notifications
You must be signed in to change notification settings - Fork 2
How Does ToFeedIterator Work
.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 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.
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:
-
GetItemLinqQueryable<T>()returns a queryable backed by the SDK's realCosmosLinqQueryProvider -
.ToFeedIterator()works because it's called on the real SDK LINQ provider — there's no fake to break it - The SDK translates your LINQ expression tree into Cosmos SQL internally
- The SDK serializes the SQL as an HTTP POST to the Cosmos endpoint
-
FakeCosmosHandlerintercepts the HTTP request before it leaves the process -
The handler parses the SQL (via
CosmosSqlParser), executes it against the backingInMemoryContainer, 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.
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.
// 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
}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:
- Reads the SQL from the POST body
- Extracts partition key, max item count, continuation token, and range ID from HTTP headers
- Checks the query result cache (for pagination continuations)
- 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 byHandleOrderByQueryAsyncwhich re-wraps results withorderByItemsandpayloadaliases. -
Regular queries: Simplified via
CosmosSqlParser.SimplifySdkQuery()to strip SDK-internal decorations, then delegated toInMemoryContainer.GetItemQueryIterator<JToken>()
-
ORDER BY queries: Detected structurally (the SDK wraps ORDER BY in a special
- Filters by partition key range (for multi-range fan-out testing)
- Builds a paged HTTP response via
BuildPagedResponsewithx-ms-continuationheaders
Deprecated. This approach is no longer needed. All recommended integration methods now use
FakeCosmosHandler, which handles.ToFeedIterator()natively. TheCosmosDB.InMemoryEmulator.ProductionExtensionspackage is deprecated.
The legacy approach was designed for tests that used raw InMemoryContainer directly (not via FakeCosmosHandler). It required:
- Adding the
CosmosDB.InMemoryEmulator.ProductionExtensionsNuGet package to production code - Changing every
.ToFeedIterator()call to use the legacy extension method - 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.
- Getting Started — quick-start setup for both DI and non-DI tests
- Choosing Your Approach — comparison of integration strategies
- Setup Guide — DI patterns that handle feed iterator wiring automatically
- SQL Queries — supported SQL syntax (relevant to the LINQ-to-SQL translation)
- What's New in 4.0 — all changes in v4.0
Getting Started
Integration & Dependency Injection
Data Management
Reference
Help