Applications that use Azure Cosmos DB often need to test database operations before deploying changes to Azure. These tests may involve creating documents, updating records, executing queries, validating partition keys, or retrieving data for an AI-powered feature.
Using a live Azure account for every development test can introduce unnecessary costs, network dependencies, and data management problems. Developers may also hesitate to run destructive tests against a shared development database.
The Azure Cosmos DB Emulator provides a local environment for developing and testing supported Cosmos DB APIs without connecting to a live database account. It can be used during application development and in automated integration tests.
For AI applications, the emulator is useful for testing the database layer behind features such as document retrieval, conversation history, metadata filtering, and retrieval-augmented generation (RAG).
This article explains how to run the emulator, connect a C# application using the Cosmos DB .NET SDK, and write integration tests that verify database-dependent application behavior.
Why Use the Azure Cosmos DB Emulator?
Unit tests usually replace database dependencies with mocks or in-memory implementations. These tests are fast, but they do not prove that the application works correctly with the actual Cosmos DB SDK and query behavior.
For example, a mocked repository may return the expected document even when the production query uses an incorrect partition key. The test passes because the mock does not reproduce the database operation.
An emulator-based integration test can exercise the real SDK against a local Cosmos DB environment.
Common scenarios include:
- Testing document creation, retrieval, replacement, and deletion.
- Verifying partition-key configuration.
- Checking queries and filtering logic.
- Testing repository implementations.
- Validating the database layer used by an AI retrieval pipeline.
- Running integration tests in a continuous integration workflow.
The emulator is intended for development and testing, not production workloads. It also does not reproduce every behavior of the cloud service, so cloud-specific functionality must be validated separately.
Start the Cosmos DB Emulator on Windows
There are multiple emulator deployment options, including a Windows-local installation and Docker-based versions.
For a container-based setup, the newer vNext emulator provides a practical option across supported x64 and ARM64 environments. Its image and supported features differ from those of the older Windows-local emulator.
Make sure Docker Desktop is installed and running before proceeding.
Pull the emulator image:
docker pull mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-latest
Start the emulator:
docker run -d `
--name cosmos-emulator `
-p 8081:8081 `
-p 8080:8080 `
-p 1234:1234 `
mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-latest
The ports expose the emulator's database endpoint and associated local interfaces. Check the startup output and the documentation for your chosen emulator version before relying on a particular port or interface.
Verify that the container is running:
docker ps
Inspect its logs if the container exits or fails to initialize:
docker logs cosmos-emulator
The emulator may take some time to initialize. Do not start integration tests until the database endpoint is ready to accept requests.
The older Windows-local emulator uses its own installation and startup process. If your team already uses that version, follow its connection and certificate configuration rather than mixing instructions from the two variants.
Connect a C# Application to the Emulator
The Azure Cosmos DB .NET SDK provides the CosmosClient class for communicating with the database.
Create a console project and install the SDK package:
dotnet new console -n CosmosLocalDemo
cd CosmosLocalDemo
dotnet add package Microsoft.Azure.Cosmos
For the standard emulator configuration, the NoSQL endpoint and well-known development key are commonly configured as follows:
using Microsoft.Azure.Cosmos;
const string endpoint = "https://localhost:8081/";
const string emulatorKey =
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
using var client = new CosmosClient(
endpoint,
emulatorKey);
These values apply to the standard emulator configuration; verify the endpoint, credentials, and TLS requirements for the specific emulator version you run. The development key is public emulator configuration, not a production credential.
The emulator's certificate must be trusted by the application environment. If the SDK reports a certificate validation error, configure the appropriate local development certificate instead of disabling TLS validation indiscriminately.
For a production application, use the actual Azure account's endpoint and an appropriate authentication mechanism, such as Microsoft Entra ID where supported.
Create a Database and Container for Testing
Cosmos DB stores items inside containers, which belong to databases. A container also defines a partition-key path that influences how items are distributed and accessed.
The following example creates a database and container if they do not already exist.
using Microsoft.Azure.Cosmos;
const string databaseName = "ApplicationDb";
const string containerName = "Documents";
using var client = new CosmosClient(
endpoint,
emulatorKey);
Database database =
await client.CreateDatabaseIfNotExistsAsync(
databaseName);
Container container =
await database.CreateContainerIfNotExistsAsync(
containerName,
partitionKeyPath: "/category");
The partition key is set to /category. Every document written to this container must contain a category property that can be used as its partition-key value.
Choosing a partition key should reflect the application's access patterns. For example, an AI document service might use a tenant identifier when queries frequently operate within one tenant. A poor partition-key choice can create inefficient queries or uneven data distribution in a cloud deployment.
The emulator can help validate that the application supplies the expected key, but it cannot fully reproduce production partition distribution or scaling behavior.
Test Document Operations
Once the database and container exist, test the same operations that the application will perform.
Consider a document used by an AI retrieval service.
public sealed class KnowledgeDocument
{
public string id { get; set; } = Guid.NewGuid().ToString();
public string category { get; set; } = string.Empty;
public string title { get; set; } = string.Empty;
public string content { get; set; } = string.Empty;
}
The following code creates a document, retrieves it by ID and partition key, and checks the stored content.
var document = new KnowledgeDocument
{
id = Guid.NewGuid().ToString(),
category = "dotnet",
title = "Dependency Injection",
content = "Dependency injection provides dependencies to a class."
};
await container.CreateItemAsync(
document,
new PartitionKey(document.category));
ItemResponse<KnowledgeDocument> response =
await container.ReadItemAsync<KnowledgeDocument>(
document.id,
new PartitionKey(document.category));
KnowledgeDocument savedDocument = response.Resource;
if (savedDocument.title != document.title)
{
throw new InvalidOperationException(
"The stored document does not match the expected title.");
}
Console.WriteLine(savedDocument.content);
The read operation supplies both the item ID and the partition-key value. This is an important distinction in Cosmos DB: an item ID alone is not sufficient to identify an item across an entire partitioned container.
Testing this behavior against the emulator can expose serialization issues, incorrect partition-key paths, and errors in repository code.
Write Integration Tests with xUnit
A repository should be tested against the emulator when the purpose is to verify actual database interactions rather than only application logic.
For example, an integration test can create a document, retrieve it, and verify its contents.
using Microsoft.Azure.Cosmos;
using Xunit;
public sealed class CosmosRepositoryTests
{
[Fact]
public async Task CreateAndReadDocument_ReturnsStoredContent()
{
string endpoint =
Environment.GetEnvironmentVariable(
"COSMOS_ENDPOINT")
?? "https://localhost:8081/";
string key =
Environment.GetEnvironmentVariable(
"COSMOS_KEY")
?? throw new InvalidOperationException(
"COSMOS_KEY is not configured.");
using var client = new CosmosClient(endpoint, key);
string databaseName =
$"TestDb-{Guid.NewGuid():N}";
Database database =
await client.CreateDatabaseAsync(databaseName);
try
{
Container container =
await database.CreateContainerAsync(
"Documents",
"/category");
var document = new KnowledgeDocument
{
id = Guid.NewGuid().ToString(),
category = "testing",
title = "Cosmos Integration Test",
content = "Verify database persistence."
};
await container.CreateItemAsync(
document,
new PartitionKey(document.category));
var result =
await container.ReadItemAsync<KnowledgeDocument>(
document.id,
new PartitionKey(document.category));
Assert.Equal(
document.content,
result.Resource.content);
}
finally
{
await database.DeleteAsync();
}
}
}
This example uses a unique database name to isolate test data and removes the database afterward. In a shared test environment, ensure each test has its own isolated resources and that cleanup runs even when assertions fail.
The example assumes the emulator is already running and that its certificate is trusted. It also uses the same KnowledgeDocument class defined earlier.
For larger test suites, initialize shared infrastructure once per test collection when appropriate, while still isolating data between tests. Avoid allowing tests to delete a database that another test is using.
Test AI Retrieval Without Calling an AI Model
An AI feature often has two separate responsibilities: retrieving relevant documents and generating a response from them.
These responsibilities should be tested independently.
For example, a retrieval component might query Cosmos DB for documents matching a category, tenant, or metadata condition. An integration test can verify that the correct documents are returned without making an expensive model request.
This provides a useful boundary:
| Test type |
What it verifies |
| Unit test |
Application logic using controlled dependencies |
| Emulator integration test |
Actual SDK operations, queries, and stored data |
| AI integration test |
The combined retrieval and model workflow |
| Cloud validation test |
Behavior that depends on real Azure configuration and service capabilities |
If the application uses vector search or another newer Cosmos DB feature, verify that the selected emulator version supports the feature and the relevant query behavior. Do not assume every cloud feature is available in every emulator release.
Best Practices for Reliable Testing
Keep Test Data Isolated
Use unique database or container names, unique item IDs, and predictable cleanup. Avoid using shared production-like data that can be modified by multiple test runs.
Reuse CosmosClient Appropriately
CosmosClient is designed to be reused. In a long-running application, register it as a singleton or manage its lifetime carefully rather than constructing a new client for every database operation.
Wait for Emulator Readiness
Starting a container does not guarantee that the database is ready. Configure readiness checks and retry transient startup failures within a bounded period.
Test Partition-Key Behavior
Include tests that verify both correct and incorrect partition-key usage. These tests can catch bugs that simple mocked repository tests may miss.
Separate Local and Production Configuration
Use environment-specific settings for endpoints and credentials. Ensure that automated tests cannot accidentally connect to a production account and modify real data.
Keep Integration Tests Repeatable
Use deterministic test inputs, clean up temporary resources, and ensure that repeated test runs do not depend on data left behind by earlier executions.
Common Problems and Troubleshooting
| Problem |
Likely cause |
What to check |
| Connection refused |
Emulator is not ready or the port is incorrect |
Check container status and port mappings |
| TLS certificate error |
The emulator certificate is not trusted |
Install or trust the correct development certificate |
| Item cannot be found |
Wrong ID or partition key |
Verify the stored item and partition-key value |
| Container creation fails |
Invalid configuration or conflicting resource names |
Check database and container setup |
| Tests fail intermittently |
Shared test data or startup timing |
Isolate resources and wait for readiness |
| Cloud behavior differs from local results |
Emulator feature or behavior limitations |
Run additional tests against an Azure test account |
Advantages and Disadvantages
The emulator makes database development more repeatable and reduces the need to use a live Azure account for every integration test. It is particularly useful for testing repository code, document persistence, queries, and database-dependent AI features.
However, it cannot reproduce every aspect of Azure Cosmos DB. Production throughput, distributed behavior, regional replication, service-specific limits, and certain newer features may differ from the local environment.
For that reason, emulator tests should complement rather than replace a smaller set of tests against a properly isolated Azure test account.
Summary
The Azure Cosmos DB Emulator provides a practical way to test database-dependent C# applications locally. By combining it with the Cosmos DB .NET SDK and xUnit, developers can verify document operations, partition-key behavior, query logic, and repository implementations without relying on a live database for every test.
For AI applications, this makes it easier to validate the retrieval layer independently before connecting it to an embedding or language model. Keep test data isolated, manage emulator certificates correctly, and verify cloud-specific behavior separately.
A reliable testing strategy combines fast unit tests, emulator-based integration tests, and targeted cloud validation. This approach helps identify database integration problems earlier while keeping the development workflow repeatable.
Join the conversation! Your thoughts help the community grow.