-
Notifications
You must be signed in to change notification settings - Fork 2
Known Limitations
This document lists all known areas where the in-memory emulator differs from real Azure Cosmos DB. These are documented so you can make informed decisions about which behaviours matter for your tests.
For a full list of supported features, see Features. For a side-by-side comparison with the official Microsoft emulator, see Feature Comparison. For SQL function coverage, see SQL Queries.
Note: Tests marked "(skipped)" document a known gap rather than a passing assertion. Tests marked "(sister)" document the emulator's actual divergent behaviour.
- Partition Keys
- Queries
- Full-Text Search
- Vector Search
-
Change Feed
- 13. Incremental Change Feed (Multiple Updates)
- 14. Processor vs Raw Iterator Deduplication
- 15.
Change FeedFIXED v2.0.163_lsnProperty Not Present - 16.
TTL Eviction Does Not Produce Change Feed EntriesFIXED v2.0.163 - 17.
Stream Change Feed Iterator Eager EvaluationFIXED - 18.
ChangeFeedProcessor Stream HandlerFIXED v4.0.7 - 19. Processor Context FeedRange Always Returns PartitionKey.None
- 20. AllVersionsAndDeletes Mode — Via Checkpoint
-
Triggers
- 21. C# Handlers (JavaScript Optional)
- 22.
PatchItemStreamAsync Does Not Fire TriggersFIXED v2.0.103 - 23.
TransactionalBatch Does Not Support TriggersFIXED v2.0.103 - 24.
Pre-Trigger Exceptions Not Wrapped as CosmosExceptionFIXED v2.0.163 - 25.
CreateItemAsync Response Does Not Reflect Pre-Trigger MutationsFIXED v2.0.163
- Stored Procedures
- Transactional Batch
- Concurrency
- TTL
-
Synthetic / Mock Metadata
- 39.
Resource IDs (FIXED v2.0.102_rid) - 40.
ETag FormatFIXED v2.0.102 - 41.
Session TokensFIXED v2.0.163 - 42. Diagnostics Object Is Synthetic
- 43.
CosmosException.Diagnostics Not PopulatedFIXED v2.0.163 - 44.
Error Sub-Status Codes Always ZeroFIXED v2.0.163 - 45. DatabaseResponse / ContainerResponse Metadata
- 46. CosmosResponseFactory
- 47. Permission Tokens
- 48. Account Metadata queryEngineConfiguration
- 49. Collection Metadata Indexing Policy
- 50. Nanosecond Precision
- 51. Request Charges (RU)
- 39.
- FeedRange & Partitioning
- State Management (Import / Export)
- PITR (Point-in-Time Restore)
- DI / Configuration
- SQL Query Edge Cases
- Not Simulated
FIXED in v2.0.163 — When the partition key field is missing from a document, the emulator no longer falls back to the
idfield. Instead, items are stored with a null partition key, matching real Cosmos DB behaviour.
Tests: PkExtractionEdgeCaseTests.SinglePk_MissingField_StoresWithNullPartitionKey, PkExtractionConsistencyTests.PartitionKey_None_PathMissing_StoresWithNullPk
Real Cosmos DB: PartitionKey.None and PartitionKey.Null have different semantics. None means "extract PK from document body" while Null means "the partition key value is explicitly null".
Emulator: Both PartitionKey.None and PartitionKey.Null are mapped to the same internal representation. Items created with one can be read with the other.
Impact: Low. Only affects code that distinguishes between documents with no partition key vs documents with an explicit null partition key.
Test: PartitionKeyNoneVsNullTests.PartitionKeyNoneVsNull_EmulatorBehavior_TreatedIdentically
Real Cosmos DB: Continuation tokens are opaque, base64-encoded JSON strings containing internal cursor state, partition information, and version metadata.
Emulator: Uses base64-encoded JSON tokens ({"v":2,"key":"...","offset":N}) — closer to real Cosmos format but still simpler internally.
Impact: Low. Only matters if your code parses or inspects the continuation token format. Pagination behaviour (requesting pages, resuming from a token) works correctly.
Tests: ContinuationTokenFormatTests.ContinuationToken_EmulatorBehavior_IsPlainIntegerOffset, InMemoryFeedIterator_ContinuationToken_IsOffsetBased_Divergent
Real Cosmos DB: ARRAY_CONTAINS_ANY(arr, val1, val2, ...) uses variadic syntax where each argument after the array is a single scalar value to search for.
Emulator: Additionally supports array form ARRAY_CONTAINS_ANY(arr, [val1, val2]) as a convenience. When the second argument is a document property that is an array (e.g., ARRAY_CONTAINS_ANY(c.tags, c.otherTags)), the emulator iterates the array's elements. Real Cosmos DB would treat the array as a single value to search for.
Impact: Low. The variadic form (official Cosmos syntax) works correctly.
Tests: ArrayContainsAny_DocumentArrayArg_RealCosmosTreatsSingleValue (skipped), ArrayContainsAny_DocumentArrayArg_EmulatorIteratesElements
Real Cosmos DB: Hybrid search queries (combining vector distance with full-text search via ORDER BY RANK RRF(...)) produce a hybridSearchQueryInfo field in the query plan.
Emulator: Does not produce hybridSearchQueryInfo. Hybrid search queries are not supported through the gateway query plan endpoint.
Impact: Low. Vector search and full-text search work independently.
Tests: QueryPlan_HybridSearch_SetsHybridSearchQueryInfo (skipped), QueryPlan_HybridSearch_DivergentBehavior_IgnoredGracefully (sister)
All metadata iterators (GetDatabaseQueryIterator, GetContainerQueryIterator, GetStoredProcedureQueryIterator, etc.) support WHERE c.id = '...' filtering. Arbitrary WHERE clauses beyond c.id are ignored.
All 6 geospatial functions implemented with real geometry (haversine, point-in-polygon, spherical excess); results may differ slightly from Cosmos DB's internal spatial engine. See SQL Queries — Geospatial.
Real Cosmos DB: Full-text search requires a full-text indexing policy on the container. Without it, all FULLTEXT* queries return HTTP 400. Functions use NLP tokenization with stemming and stop-word removal. FULLTEXTSCORE computes BM25 relevance scores with inverse document frequency and document length normalization.
Emulator: Full-text functions work on any container without indexing configuration — no FullTextPolicy or FullTextIndexes on IndexingPolicy required. Uses case-insensitive substring matching (no stemming). FULLTEXTSCORE returns a naive term-frequency count (total occurrences of search terms). Stop words are not removed — searching for "the" matches any text containing that substring.
Impact: Low. Relative ordering is usually correct. Absolute scores differ. No stemming means "running" won't match "runs". No indexing policy validation means queries won't fail on misconfigured containers (real Cosmos would return 400).
Workaround: For BM25 ranking or stop-word behaviour, test with the real Cosmos DB. The emulator is suitable for testing query structure and basic filtering logic.
Tests: FullTextContains_StopWords_EmulatorMatchesAll (sister), FullTextContains_StopWordRemoval_ShouldIgnoreCommonWords (skipped)
Real Cosmos DB: Requires a vectorEmbeddings container policy (path, dataType, dimensions, distanceFunction) and optionally a vectorIndexes indexing policy (flat, quantizedFlat, diskANN). Flat index limited to 505 dimensions; quantizedFlat/diskANN to 4096. TOP N is recommended with ORDER BY VectorDistance.
Emulator: No vector policy or index configuration needed. VECTORDISTANCE works immediately with brute-force exact computation. No dimensionality limits (tested up to 2000). No TOP N requirement.
Impact: Low. The emulator gives correct distance/similarity results. The only difference is that real Cosmos DB requires upfront configuration and has performance-related constraints that don't apply to in-memory testing.
Tests: VectorDistance_RequiresVectorPolicy_InRealCosmos (skipped), VectorDistance_FlatIndexMax505Dimensions_InRealCosmos (skipped), VectorDistance_RequiresTopNWithOrderBy_InRealCosmos (skipped), VectorDistance_FiveArgs_RealCosmosRejectsExtraArgs, VectorDistance_UnknownDistanceFunction_RealCosmosRejects, VectorDistance_ParameterizedQuery_RealCosmosSupports (skipped), VectorDistance_WithNaNInVector_RealCosmosBehaviour (skipped)
If extreme values (e.g. 1e308) produce Infinity or NaN in dot product or euclidean distance calculations, the emulator returns null instead of a non-finite value. This prevents invalid JSON serialization. Real Cosmos DB is bounded by dimension limits and typically contains normalised embeddings.
Real Cosmos DB (Incremental mode): Returns only the latest version of each item, but the timing and batching of "latest" can vary based on physical partition distribution.
Emulator: Strictly returns only the latest version per item across all updates.
Impact: None for most applications. The in-memory behaviour is actually more predictable.
ChangeFeedProcessor<T> delivers only the latest version per item (matching real Cosmos behavior). Raw GetChangeFeedIterator<T> with checkpoint still delivers all intermediate versions. Handlers should be idempotent.
Test: ChangeFeed_Processor_DeliversOnlyLatestVersion
FIXED in v2.0.163 — Change feed items now include a
_lsn(logical sequence number) property that increments with each write operation, matching real Cosmos DB behaviour.
Tests: Verified via existing change feed tests.
FIXED in v2.0.163 — TTL eviction now records delete tombstones in the change feed (marked with
_ttlEviction: true). These tombstones are visible via the checkpoint-based change feed API (GetChangeFeedIterator<T>(checkpoint)).RestoreToPointInTimecorrectly skips TTL eviction tombstones so that PITR does not treat TTL-expired items as explicitly deleted.
Tests: ChangeFeed_TTL_Eviction_RecordsDeleteInChangeFeed, ChangeFeed_TTL_ExpiredItem_SilentlyRemoved_NoChangeFeedEntry (sister — retained for reference)
FIXED —
GetChangeFeedStreamIterator(ChangeFeedStartFrom.Now())now uses lazy evaluation via a factory delegate, consistent with the typedGetChangeFeedIterator<T>. Items added after iterator creation with timestamps after the creation time ARE visible on subsequentReadNextAsynccalls.
Tests: ChangeFeed_Now_ThenAddItems_StreamIterator_MayNotSeeNewItems (passing — verifies items added post-creation are visible)
FIXED in v4.0.7 —
GetChangeFeedProcessorBuilderwith aStreamchange handler now works correctly. TheChangeFeedProcessorBuilderFactoryuses reflection to construct the builder without requiring the leaseContainerto be cast toContainerInternal. Use.WithInMemoryLeaseContainer()instead of.WithLeaseContainer()and both typed and stream change feed processors work identically.
Tests: ChangeFeedProcessorBuilder_StreamHandler_ShouldInvokeHandler
Real Cosmos DB: ChangeFeedProcessorContext.FeedRange returns the FeedRangeEpk of the specific partition range being processed by the lease.
Emulator: Always returns FeedRange.FromPartitionKey(PartitionKey.None) since the emulator uses a single-lease model without partition-level tracking.
Impact: Low. Only affects code that inspects context.FeedRange inside a change feed processor handler.
Tests: ChangeFeed_Processor_Context_FeedRange_EmulatorBehavior_AlwaysReturnsNone (sister), ChangeFeed_Processor_Context_FeedRange_ReflectsActualProcessingRange (skipped)
Use GetChangeFeedIterator<T>(checkpoint) for all versions and delete tombstones; ChangeFeedMode.AllVersionsAndDeletes is internal in older SDK versions. See Features — Change Feed.
Real Cosmos DB: Pre-triggers and post-triggers execute server-side JavaScript before/after create, replace, and delete operations. A pre-trigger can modify the document before it's persisted.
Emulator: Triggers execute via RegisterTrigger() with C# handlers (Func<JObject, JObject> for pre-triggers, Action<JObject> for post-triggers). JavaScript trigger bodies registered via CreateTriggerAsync are stored but not interpreted by default. To enable JavaScript trigger execution, install the optional CosmosDB.InMemoryEmulator.JsTriggers package and call container.UseJsTriggers(). Pre-triggers can modify documents before persistence; post-trigger failures roll back the write.
Impact: Low. Use RegisterTrigger() to implement trigger logic in C# instead of JavaScript, or use UseJsTriggers() to interpret JS bodies. Trigger invocations are controlled via ItemRequestOptions.PreTriggers / PostTriggers, matching the real SDK API surface. See Features — Triggers for usage examples.
Tests: TriggerExecutionTests.PreTrigger_ShouldModifyDocumentOnCreate, TriggerExecutionTests.PreTrigger_CreateTriggerAsyncAlone_DoesNotFireWithoutRegisterTrigger
FIXED in v2.0.103 —
PatchItemStreamAsyncnow correctly fires pre-triggers and post-triggers when specified inPatchItemRequestOptions, matching the typedPatchItemAsyncbehaviour. Post-trigger failures roll back the patch operation.
Tests: PatchItemStreamAsync_PreTrigger_Fires, PatchItemStreamAsync_PostTrigger_Fires, PatchItemStreamAsync_PostTrigger_RollbackOnError
FIXED in v2.0.103 — Triggers can now be specified for batch operations via the
Propertiesdictionary onTransactionalBatchItemRequestOptionsorTransactionalBatchPatchItemRequestOptions, using the standard Cosmos DB HTTP header keys:new TransactionalBatchItemRequestOptions { Properties = new Dictionary<string, object> { ["x-ms-pre-trigger-include"] = new[] { "myPreTrigger" }, ["x-ms-post-trigger-include"] = new[] { "myPostTrigger" } } }Triggers are only invoked when explicitly specified via Properties. Batch operations without trigger Properties continue to behave as before (no triggers fired).
Tests: Batch_CreateItem_WithPreTrigger_FiresTrigger, Batch_PatchItem_WithPreTrigger_FiresTrigger, Trigger_Js_TransactionalBatch_WithPropertiesHeader, Batch_TriggerFailure_CausesRollback
FIXED in v2.0.163 — Pre-trigger exceptions from C# handlers are now wrapped in a
CosmosExceptionwithHttpStatusCode.BadRequest, matching real Cosmos DB behaviour.CosmosExceptioninstances thrown by handlers are re-thrown as-is.
Tests: PreTrigger_ThrowingHandler_AbortsCreate, PreTrigger_ThrowingHandler_AbortsUpsert, PreTrigger_ThrowingHandler_AbortsReplace
FIXED in v2.0.163 —
CreateItemAsync,UpsertItemAsync, andReplaceItemAsyncnow return the stored (post-trigger, post-system-property-enrichment) document inItemResponse<T>.Resource, matching real Cosmos DB behaviour.
Tests: PreTrigger_ShouldModifyDocumentOnCreate
CreateStoredProcedureAsync creates metadata in _storedProcedureProperties; RegisterStoredProcedure registers a C# handler in _storedProcedures. These are independent stores. A handler can exist without metadata (404 on Read but Execute works) and vice versa. DeleteStoredProcedureAsync removes from both stores, but DeregisterStoredProcedure only removes the handler.
Tests: StoredProcedureDualStoreTests (7 tests)
CreateStoredProcedureAsync stores JavaScript body as metadata. Use RegisterStoredProcedure() for C# handlers, or install the CosmosDB.InMemoryEmulator.JsTriggers package and call container.UseJsStoredProcedures() to execute JavaScript bodies including getContext().getCollection() CRUD operations — all partition-scoped.
Tests: ExecuteStoredProcedure_JavaScriptBody_ShouldExecute, JsSproc_CreateDocument_InsertsItem, JsSproc_BulkDelete_Pattern
C# handlers registered via RegisterStoredProcedure() have unrestricted container access via closure. JavaScript stored procedures (via UseJsStoredProcedures()) are automatically partition-scoped — getContext().getCollection() operations only access documents within the partition key passed to ExecuteStoredProcedureAsync. For C# handlers, scope your queries using QueryRequestOptions.PartitionKey.
Tests: JsSproc_PartitionScoped_CannotAccessOtherPartition, StoredProcedure_CrossPartition_EmulatorWorkaround_ScopeManually
FIXED in v2.0.163 — Delete and read operations now contribute to the estimated batch size using
Encoding.UTF8.GetByteCount(id) + 128bytes, matching real Cosmos DB's inclusion of operation metadata in the 2MB batch payload limit.
Tests: Batch_DeleteOperations_ContributeToBatchSize
response.Diagnostics.GetClientElapsedTime() always returns TimeSpan.Zero because there is no real network I/O.
Tests: Batch_Diagnostics_ContainsRequestLatency (skipped), Batch_Diagnostics_InMemory_ReturnsZeroElapsedTime (sister)
response.RequestCharge always returns 1.0 regardless of operation count or type. Real Cosmos DB charges per operation within the batch.
Tests: Batch_RequestCharge_ScalesWithOperationCount (skipped), Batch_RequestCharge_InMemory_AlwaysReturns1RU (sister)
Batch response headers don't include real session tokens. Real Cosmos DB returns per-operation LSN-based session tokens.
Test: Batch_Headers_ContainSessionToken (skipped)
The emulator's batch uses snapshot/restore for atomicity but doesn't hold a global lock during execution. Concurrent direct CRUD can see intermediate batch state.
Impact: Low — only relevant in multi-threaded test scenarios mixing batch + direct CRUD.
Test: Batch_ConcurrentBatchAndDirectCrud_IsolationGuaranteed (skipped)
Writes to _items, _etags, _timestamps are not atomic relative to each other. Under extreme concurrency, a reader may briefly see inconsistent state (e.g. new item value with old ETag). Final state is always consistent.
Mitigated in v2.0.137 — All mutation operations (Create, Upsert, Delete, Replace, Patch — both typed and stream variants) on the same item are now serialised via a per-item async mutex (
_itemLocks). This prevents concurrent mutations from interleaving dictionary writes (e.g. a concurrent patch resurrecting a deleted item, or an upsert losing to a stale patch read-modify-write). The three-dictionary non-atomicity for readers still applies.
FIXED in v2.0.137 — All mutation operations (Create, Upsert, Delete, Replace, Patch — both typed and stream variants) are now serialised per item via an async mutex (
_itemLocks). This makes ETag validation (IfMatchEtag) atomic with the subsequent write, preventing concurrent ETag-protected writes from both succeeding. See also #34.
Tests: MultipleConcurrentConditionalWrites_OnlyOneSucceeds
Batch execution is not globally isolated from non-batch operations. Concurrent readers may see intermediate batch state during execution or briefly see empty state during rollback.
Expired items are only filtered out when a read or query accesses them. Real Cosmos DB proactively evicts expired items via a background process.
Partial fix in v2.0.162 —
ItemCountnow filters TTL-expired items, so the count accurately reflects live items.ExportStatealso filters expired items.
Tests: TTL_ProactiveEviction_ShouldDeleteWithoutRead (skipped), Divergent_TTL_LazyEviction_ItemVisibleUntilAccessed
Queries filter out expired items from results but do NOT evict them from the in-memory dictionaries. Items remain in _items, _etags, and _timestamps until a direct CRUD operation triggers EvictIfExpired(). Memory usage may be slightly higher than expected with TTL-heavy workloads.
Tests: Query_ShouldEvictExpiredItemsFromMemory (skipped), Query_EmulatorFiltersButDoesNotEvictExpiredItems (sister)
These items document where the emulator returns structurally valid but simplified metadata compared to real Cosmos DB. None affect functional correctness.
FIXED in v2.0.102 —
_ridnow uses a hierarchical 8-byte format (4-byte container hash via MurmurHash3 + 4-byte monotonic document counter), base64-encoded. This matches the real Cosmos DB pattern of hierarchical encoded resource IDs._selfstill uses a synthetic URI path._attachmentsis always"attachments/"(matches real Cosmos).
FIXED in v2.0.102 — ETags are now quoted monotonically increasing 16-digit hex counters (e.g.
"00000000000001a3"), providing ordering semantics similar to real Cosmos DB's timestamp-based values. Optimistic concurrency (IfMatchEtag) continues to work correctly.
Tests: BehavioralDifferenceTests.ETag_IsQuotedMonotonicHex, ETagResponseTests.ETag_Format_IsQuotedHex_OnAllWriteOperations, ETag_IsMonotonicallyIncreasingHexCounter
FIXED in v2.0.163 — Session tokens now use the format
0:{N}#{N}where N increments with each write operation, providing monotonically increasing values that match real Cosmos DB’s progression semantics. Read operations return the current (latest) token value.
Tests: SessionToken_IsSyntheticGuidFormat, SessionToken_Consistent_AcrossTypedAndStream
CosmosDiagnostics returns a shared InMemoryDiagnostics instance (a concrete subclass) with GetClientElapsedTime() returning TimeSpan.Zero and an empty ToString(). Feed iterators and transactional batches use their own diagnostic subclasses (InMemoryFeedDiagnostics, InMemoryBatchDiagnostics), all returning TimeSpan.Zero.
Test: Diagnostics_ReturnsMock_EmptyToString
FIXED in v2.0.163 —
CosmosExceptioninstances thrown by the emulator are nowInMemoryCosmosException(a public subclass) that overridesDiagnosticsto return a non-nullCosmosDiagnosticswithGetClientElapsedTime() => TimeSpan.ZeroandToString() => "{}".
Tests: CosmosException_HasDiagnostics_OnError
FIXED in v2.0.163 — Container and database
404 NotFoundexceptions now includeSubStatusCode = 1003, matching real Cosmos DB’s fine-grained error classification. Other error codes remain atSubStatusCode = 0.
Tests: CosmosException_SubStatusCode_AlwaysZero
DatabaseResponse and ContainerResponse are NSubstitute mocks that only set StatusCode and Resource. RequestCharge, ActivityId, Diagnostics, and Headers are not populated. Use stream APIs (CreateDatabaseStreamAsync, ReadStreamAsync, etc.) which do return proper headers.
Tests: DatabaseResponse_HasRequestCharge_ActivityId_Diagnostics (skipped), DatabaseResponse_EmulatorBehavior_OnlyHasStatusCodeAndResource (sister), ContainerResponse_HasRequestCharge_ActivityId_Diagnostics (skipped), ContainerResponse_EmulatorBehavior_OnlyHasStatusCodeAndResource (sister)
CosmosClient.ResponseFactory returns an NSubstitute mock whose methods return default/null. Real SDK provides a factory for deserializing response messages.
Permission tokens have format type=resource&ver=1&sig=stub_<id> with fake signatures. Real Cosmos generates HMAC-signed resource tokens.
Returns hardcoded permissive configuration that allows all query features. Real Cosmos DB returns configuration reflecting actual query engine limits.
Tests: AccountMetadata_QueryEngineConfiguration_MatchesRealCosmos (skipped), AccountMetadata_QueryEngineConfiguration_IsPermissive_Divergence (sister)
Returns a simplified permissive default that does not restrict any query patterns. Real Cosmos DB returns the actual configured indexing policy.
Tests: CollectionMetadata_IndexingPolicy_MatchesRealCosmos (skipped), CollectionMetadata_IndexingPolicy_ReturnsPermissiveDefault_Divergence (sister)
Both emulator and real Cosmos DB have 100-nanosecond (1 tick) resolution. DateTimeAdd('ns', 50, ...) truncates to 0 ticks. This is matching behaviour, documented here for reference.
All operations return a synthetic request charge of 1.0 RU. There is no attempt to simulate real RU consumption.
Real Cosmos DB: Returns multiple FeedRange instances based on physical partition distribution.
Emulator: Defaults to 1 FeedRange. Set FeedRangeCount to a higher value to simulate multiple physical partitions. FeedRange-scoped queries and change feed iterators use MurmurHash3-based partition key hashing to filter items to the correct range.
Impact: Low. Use container.FeedRangeCount = N for code that parallelises across feed ranges. See Features — FeedRange Support for examples.
Test: FeedRangeDivergentBehaviorTests.GetFeedRanges_DefaultsSingle_SetFeedRangeCountForMultiple
Real Cosmos DB: Uses actual range-based partitioning with server-managed splits and merges.
InMemoryEmulator: Divides the uint32 hash space into equal-width intervals (via MurmurHash3). Items are assigned to feed ranges based on which interval their partition key hash falls in. Queries return correct results, but range IDs and fan-out mechanics differ from real Cosmos DB.
Tests: MultiPartition_FanOut_QueryExecutesAcrossAllRanges (skipped), MultiPartition_FanOut_UsesSimplifiedHashModulo_Divergence (sister)
FIXED in v2.0.163 —
ImportState()with JSON that lacks an"items"key is now a no-op that preserves existing data.ClearItems()only runs when the"items"array is present.
Tests: ImportState_MissingItemsKey_PreservesExistingData, ImportState_EmptyJsonObject_PreservesExistingData
FIXED in v2.0.163 —
ImportState()now throwsInvalidOperationExceptionif an imported item has no"id"property, matching real Cosmos DB’s requirement for an"id"field.
Tests: ImportState_ItemsMissingId_ThrowsInvalidOperation
FIXED in v2.0.163 — When an imported item lacks the partition key field, it is now stored under
PartitionKey.Noneinstead of falling back to the document’sidvalue, matching real Cosmos DB behaviour.
Tests: ImportState_ItemMissingPartitionKeyField_Behavior
ExportState() enumerates a ConcurrentDictionary which provides a point-in-time snapshot of keys but may include some concurrent writes.
Test: ExportState_DuringConcurrentWrites_MayNotBeAtomicSnapshot
Real Cosmos DB: PITR creates a new account from continuous backup — you can't multi-restore on the same container.
Emulator: RestoreToPointInTime replays the in-memory append-only change feed. After restoring to T1 and making new writes, a second restore to T2 > T1 replays original entries between T1 and T2, resurrecting items the first restore removed. Call ClearItems() between restores for isolated scenarios.
Tests: RestoreToPointInTime_PostRestoreWritesShouldNotAffectEarlierRestore (skipped), RestoreToPointInTime_PostRestoreWritesThenSecondRestore_ActualBehavior
Real Cosmos DB: Continuous backup retains all changes within the retention window (7–30 days).
Emulator: The change feed retains up to MaxChangeFeedSize entries (default 1000). If more mutations occur than the cap allows, the oldest entries are evicted and RestoreToPointInTime may produce incomplete results — items whose only record was in evicted entries will be missing from the restored state.
Workaround: Set MaxChangeFeedSize = 0 to disable eviction if your tests rely on PITR across large numbers of writes.
Impact: Very low. PITR is rarely used, and most test scenarios involve far fewer than 1000 mutations.
RegisterStoredProcedure() and RegisterTrigger() are methods on IContainerTestSetup, not available on the abstract Container class resolved from Dependency Injection. Use OnContainerCreated to capture the IContainerTestSetup, or OnHandlerCreated to capture FakeCosmosHandler and cast handler.BackingContainer to IContainerTestSetup.
Tests: StoredProcedure_ViaWaf_RequiresBackingContainerAccess (skipped), Trigger_ViaWaf_RequiresBackingContainerAndHeaders (skipped)
Deleting a container via DeleteContainerAsync() removes it from the in-memory database, but the DI container retains its reference to the now-deleted singleton. Subsequent DI resolutions return the same deleted instance.
Test: DeleteContainer_ViaWaf_DiStillHoldsReference (skipped)
UseInMemoryCosmosDB<TClient>() uses a Castle.Core dynamic proxy to create a runtime subclass of your production TClient. This has the following constraints:
| Limitation | Detail |
|---|---|
| Constructor requirement |
TClient must have a public constructor accepting (string connectionString, CosmosClientOptions options)
|
| Not sealed |
TClient must not be sealed — Castle cannot subclass sealed types |
| Type identity |
client.GetType() != typeof(TClient) — the resolved instance is a proxy subclass. client is TClient is true, but exact type checks will fail |
| Constructor side effects | If your constructor does more than call base(...), those side effects run with a fake connection string |
| Debugger display | The debugger shows the proxy type name (e.g. EmployeeCosmosClientProxy) rather than EmployeeCosmosClient
|
| NativeAOT | Castle uses Reflection.Emit — incompatible with NativeAOT (irrelevant for test code) |
Tests: TypedClient_IsCastleProxy, OnClientCreatedCallback
62 resolved in v2.0.105 (DISTINCT now uses structural equality via JToken.DeepEquals, correctly deduplicating objects with different property ordering — e.g. after Patch operations that append new properties).
63–69 resolved in v2.0.83 (three-value logic for NOT/LIKE/comparisons, HAVING compound AND, SELECT VALUE ternary+aggregate, ToString(bool) casing, ORDER BY array/object element-by-element comparison, DateTimePart weekday support).
70–78 resolved in v2.0.85 (NaN/Infinity → undefined in math/arithmetic, DateTime functions null → undefined, SUBSTRING null → undefined, GROUP BY null vs undefined key collision, multi-JOIN source alias resolution, ARRAY_CONTAINS with parameter array, static DateTime.UtcNow race condition in queries).
79–81 resolved in v2.0.86 (SUBSTRING negative start/length threw ArgumentOutOfRangeException, LEFT/RIGHT negative count threw index exception).
82 resolved in v2.0.87 (ORDER BY aggregate expression after GROUP BY was re-evaluating instead of resolving to SELECT alias, causing non-deterministic ordering on Linux CI).
83 resolved in v2.0.88 (TOSTRING(null) and TOSTRING(array/object) returned values instead of undefined).
84–91 resolved in v2.0.90 (CONCAT null→undefined, REPLICATE negative/>10000→undefined, ROUND negative precision, NumberBin zero/negative→undefined, arithmetic on null→undefined, NOT null→undefined, StringSplit empty delimiter→undefined, object parameter equality via JToken.DeepEquals).
92–95 resolved in v2.0.91 (integer arithmetic overflow silently wrapping to negative instead of returning double, DATETIMEADD unhandled ArgumentOutOfRangeException on year overflow, CHOOSE out-of-bounds returning null instead of undefined, GetCurrentDateTime test flaky across timezones due to missing RoundtripKind parse).
96–102 resolved in v2.0.92 (DATETIMEDIFF null args returned null instead of undefined, TICKSTODATETIME null input returned null instead of undefined, DATETIMETOTIMESTAMP null input returned null instead of undefined, TIMESTAMPTODATETIME null input returned null instead of undefined, DATETIMEBIN negative/zero binSize returned null instead of undefined, RAND() not thread-safe — used new Random() instead of Random.Shared, ChangeFeed_Processor_DeliversOnlyLatestVersion flaky race condition).
103–106 resolved in v2.0.93 (BinaryOpToString missing bitwise operator symbols, REGEXMATCH with invalid regex pattern threw ArgumentException instead of returning undefined, ARRAY_CONCAT with null argument returned empty array instead of undefined, IS_OBJECT/IS_ARRAY with parameter values always returned false).
107–112 resolved in v2.0.94 (GROUP BY hasAggregate detection missed aggregates nested inside object/array literals — e.g. SELECT VALUE {"cnt": COUNT(1)}, BitwiseOp returned null instead of undefined for non-integer inputs, MathOp (ABS/FLOOR/CEILING etc.) returned null instead of undefined for null inputs, STRING_EQUALS returned null instead of undefined for null arguments, string concat (||) with null operand returned null instead of undefined, HAVING COUNT(c.field) didn't exclude null values — inconsistent with SELECT-level COUNT).
113–115 resolved in v2.0.95 (INTDIV/INTMOD by zero returned null instead of undefined, ARRAY_LENGTH on non-array returned null instead of undefined).
116–120 resolved in v2.0.96 (LEFT/RIGHT negative count returned null instead of undefined, TOBOOLEAN invalid input/null/number returned null instead of undefined, INTBITLEFTSHIFT/INTBITRIGHTSHIFT with shift < 0 or >= 64 had undefined C# behaviour instead of returning undefined, NOT NOT on undefined expression incorrectly included rows instead of propagating undefined through EvaluateWhereExpressionIncludesUndefined).
121–124 resolved in v2.0.97 (Unary negate on null/undefined/non-numeric returned null instead of undefined, bitwise NOT on null/undefined/non-integer returned null instead of undefined, LIKE with null operand returned false instead of undefined — also fixed NOT LIKE null propagation through EvaluateWhereExpressionIncludesUndefined, COALESCE function returned null instead of undefined when all arguments are undefined).
125–129 resolved in v2.0.104 (STRINGTOARRAY/STRINGTOOBJECT/STRINGTONUMBER/STRINGTOBOOLEAN/STRINGTONULL returned null instead of undefined for null input, DATETIMEADD with invalid date-part string returned null instead of undefined).
127–133 resolved in v2.0.164 (DISTINCT + OFFSET/LIMIT applied in wrong order — OFFSET/LIMIT was applied before DISTINCT instead of after, producing incorrect results when underlying data contains duplicates; HAVING COUNT(c.field) counted null-valued fields — SelectToken(path) != null returned true for JSON null; string functions LOWER/UPPER/TRIM/LTRIM/RTRIM accepted non-string arguments via .ToString() instead of returning undefined; CONCAT accepted non-string arguments instead of returning undefined; STARTSWITH/ENDSWITH/CONTAINS accepted non-string arguments instead of returning undefined; SUBSTRING/REPLACE/INDEX_OF accepted non-string first argument instead of returning undefined; REPLICATE/STRING_EQUALS/REGEXMATCH/LEFT/RIGHT accepted non-string arguments instead of returning undefined).
134–135 resolved in v2.0.165 (MathOp functions (ABS/FLOOR/CEILING/ROUND/etc.) always returned double even when input was an integer and result was a whole number — Cosmos DB preserves integer types, so ABS(-3) on a stored integer should return integer 3; TYPE() function catch-all branch returned "Undefined" with capital U instead of lowercase "undefined" matching Cosmos DB convention).
These areas are accepted/stored but have no runtime effect, or are not implemented.
| Area | Status | Notes |
|---|---|---|
| Analytical store (Synapse) | ❌ Not simulated | OLAP context not available |
| IndexingPolicy | Accepted and stored but doesn't affect query performance | |
| Throughput (RU/s) | Persisted via ReplaceThroughputAsync and returned by ReadThroughputAsync, but has no effect on performance or throttling |
|
| Conflict resolution policy | Policy is stored on ContainerProperties and returned but not enforced at runtime. See ConflictResolutionPolicyTests.ConflictResolution_EmulatorBehavior_PolicyStoredButNotEnforced
|
|
| Composite index not required | Multi-field ORDER BY works without composite index definitions. Real Cosmos requires composite indexes for multi-field ordering |
|
| Users / permissions | ✅ Stub store | CRUD operations return synthetic responses with fake tokens; no authorization enforced. See Features — Users & Permissions |
| Consistency levels | ℹ️ Strong only | All operations execute with immediate consistency. There is no simulation of eventual, session, or bounded-staleness consistency |
| Client encryption keys | ❌ Not implemented | Requires Azure Key Vault; not meaningful for in-memory testing |
Getting Started
Integration & Dependency Injection
Data Management
Reference
Help