Skip to content

Seeding Data

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

Guide to populating the in-memory emulator with test data. Covers all Dependency Injection patterns, direct instantiation, shared fixtures, bulk seeding, state persistence, and automatic state persistence between test runs.

Overview

There are five main approaches to seeding data, each suited to different situations:

Approach When to use
InMemoryCosmos callbacks (configureContainer / ImportState() / ImportStateFromFile()) Seed data with InMemoryCosmos — simplest for non-Dependency Injection tests
Dependency Injection callbacks (OnHandlerCreated / OnContainerCreated) Seed during service registration — data is ready before the first test runs
SDK methods (CreateItemAsync / UpsertItemAsync) Seed through the real SDK pipeline — highest fidelity, per-test data
State persistence (ImportState / ImportStateFromFile) Restore a pre-built dataset from JSON — fast, repeatable, shareable
Direct container access (resolve from Dependency Injection or capture reference) Seed after the host is built but before tests run
Automatic persistence (StatePersistenceDirectory) Automatically save/restore container state between test runs — see State Persistence

All approaches work with every Dependency Injection pattern and with direct instantiation.


Seeding with InMemoryCosmos

During creation (configureContainer callback)

The configureContainer callback provides an IContainerTestSetup — use it for state import, stored procedure/UDF/trigger registration, and configuration:

using var cosmos = InMemoryCosmos.Create("orders", "/customerId",
    configureContainer: setup =>
    {
        setup.ImportState("""
            {
              "items": [
                { "id": "order-1", "customerId": "cust-1", "total": 99.99 }
              ]
            }
            """);
    });

For item-by-item seeding via SDK methods, seed after creation instead:

After creation (ImportState)

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

// Import a JSON snapshot
cosmos.ImportState(File.ReadAllText("seed-data.json"));

// Or seed via the SDK
await cosmos.Container.CreateItemAsync(
    new { id = "order-1", customerId = "cust-1", total = 99.99 },
    new PartitionKey("cust-1"));

Multi-container builder

using var cosmos = InMemoryCosmos.Builder()
    .AddContainer("orders", "/customerId", setup =>
    {
        setup.ImportState("""{"items":[{"id":"1","customerId":"c1","product":"Widget"}]}""");
    })
    .AddContainer("products", "/categoryId")
    .Build();

// Seed products after build
cosmos.ImportState(productsSeedJson, "products");

Seeding via Dependency Injection Callbacks

The Dependency Injection extension methods expose callbacks that fire during service registration. These are the simplest way to seed data when using ConfigureTestServices.

UseInMemoryCosmosDB — OnHandlerCreated

OnHandlerCreated gives you the FakeCosmosHandler for each container. Access the backing container via handler.BackingContainer and seed using standard SDK methods:

builder.ConfigureTestServices(services =>
{
    services.UseInMemoryCosmosDB(options =>
    {
        options.AddContainer("orders", "/customerId");
        options.OnHandlerCreated = (containerName, handler) =>
        {
            var backing = handler.BackingContainer;
            backing.CreateItemAsync(
                new { id = "order-1", customerId = "cust-1", total = 99.99 },
                new PartitionKey("cust-1")).Wait();

            backing.CreateItemAsync(
                new { id = "order-2", customerId = "cust-2", total = 149.50 },
                new PartitionKey("cust-2")).Wait();
        };
    });
});

Note: The callback runs synchronously during Dependency Injection registration, so use .Wait() or .GetAwaiter().GetResult() for async methods. This is safe because the in-memory container never blocks.

UseInMemoryCosmosContainers — OnContainerCreated

OnContainerCreated gives you an IContainerTestSetup for state management and test configuration (stored procedures, UDFs, triggers, state import/export):

builder.ConfigureTestServices(services =>
{
    services.UseInMemoryCosmosContainers(options =>
    {
        options.AddContainer("orders", "/customerId");
        options.OnContainerCreated = setup =>
        {
            setup.ImportStateFromFile("TestData/orders-seed.json");
        };
    });
});

UseInMemoryCosmosDB<TClient> — OnClientCreated

For typed client patterns, seed through the client after it's created:

builder.ConfigureTestServices(services =>
{
    services.UseInMemoryCosmosDB<EmployeeCosmosClient>(options =>
    {
        options.AddContainer("employees", "/departmentId");
        options.OnClientCreated = client =>
        {
            var container = client.GetContainer("in-memory-db", "employees");
            container.CreateItemAsync(
                new { id = "emp-1", departmentId = "eng", name = "Alice" },
                new PartitionKey("eng")).Wait();
        };
    });
});

Seeding Multiple Containers

When your app uses multiple containers, seed each one in the OnHandlerCreated callback — it fires once per container:

services.UseInMemoryCosmosDB(options =>
{
    options.AddContainer("orders", "/customerId");
    options.AddContainer("customers", "/id");
    options.AddContainer("products", "/categoryId");

    options.OnHandlerCreated = (containerName, handler) =>
    {
        var backing = handler.BackingContainer;
        switch (containerName)
        {
            case "orders":
                backing.CreateItemAsync(
                    new { id = "order-1", customerId = "cust-1", total = 42.00 },
                    new PartitionKey("cust-1")).Wait();
                break;

            case "customers":
                backing.CreateItemAsync(
                    new { id = "cust-1", name = "Alice", tier = "gold" },
                    new PartitionKey("cust-1")).Wait();
                break;

            case "products":
                backing.CreateItemAsync(
                    new { id = "prod-1", categoryId = "widgets", name = "Deluxe Widget" },
                    new PartitionKey("widgets")).Wait();
                break;
        }
    };
});

Seeding via SDK Methods

Seed through the standard Container SDK methods for highest fidelity — your seed data goes through the same pipeline as production code.

Per-Test Seeding (WebApplicationFactory)

Resolve the Container from Dependency Injection after building the host, and seed before making requests:

public class OrderApiTests : IClassFixture<OrderApiFactory>
{
    private readonly OrderApiFactory _factory;
    private readonly HttpClient _client;

    public OrderApiTests(OrderApiFactory factory)
    {
        _factory = factory;
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetOrder_ReturnsSeededOrder()
    {
        // Seed via SDK
        var container = _factory.Services.GetRequiredService<Container>();
        await container.CreateItemAsync(
            new Order { Id = "order-1", CustomerId = "cust-1", Product = "Widget" },
            new PartitionKey("cust-1"));

        // Act
        var response = await _client.GetAsync("/api/orders/order-1?pk=cust-1");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var order = await response.Content.ReadFromJsonAsync<Order>();
        order!.Product.Should().Be("Widget");
    }
}

Per-Test Seeding (Direct Instantiation)

For tests without Dependency Injection, seed via InMemoryCosmos.Create():

public class OrderRepositoryTests : IDisposable
{
    private readonly InMemoryCosmosResult _cosmos = InMemoryCosmos.Create("orders", "/customerId");
    private Container Container => _cosmos.Container;
    public void Dispose() => _cosmos.Dispose();

    [Fact]
    public async Task GetOrder_ReturnsItem()
    {
        // Seed
        await Container.CreateItemAsync(
            new Order { Id = "order-1", CustomerId = "cust-1", Product = "Widget" },
            new PartitionKey("cust-1"));

        // Act
        var repo = new OrderRepository(Container);
        var order = await repo.GetOrder("order-1", "cust-1");

        // Assert
        order.Product.Should().Be("Widget");
    }
}

Helper Method Pattern

Extract a reusable SeedAsync method to keep tests clean:

public class OrderTests
{
    private readonly InMemoryCosmosResult _cosmos = InMemoryCosmos.Create("orders", "/customerId");
    private Container Container => _cosmos.Container;

    private async Task SeedAsync(params Order[] orders)
    {
        foreach (var order in orders)
            await Container.CreateItemAsync(order, new PartitionKey(order.CustomerId));
    }

    [Fact]
    public async Task Query_ReturnsMatchingOrders()
    {
        await SeedAsync(
            new Order { Id = "1", CustomerId = "cust-1", Product = "Widget", Total = 10 },
            new Order { Id = "2", CustomerId = "cust-1", Product = "Gadget", Total = 25 },
            new Order { Id = "3", CustomerId = "cust-2", Product = "Widget", Total = 15 });

        var results = await Container.GetItemQueryIterator<Order>(
            new QueryDefinition("SELECT * FROM c WHERE c.customerId = @pk")
                .WithParameter("@pk", "cust-1"))
            .ReadNextAsync();

        results.Should().HaveCount(2);
    }
}

Bulk Seeding via UpsertItemAsync

Use UpsertItemAsync when you need idempotent seeding — safe to run multiple times without conflict errors:

private async Task SeedAsync(Container container, IEnumerable<Order> orders)
{
    foreach (var order in orders)
        await container.UpsertItemAsync(order, new PartitionKey(order.CustomerId));
}

Parallel Bulk Seeding

For large datasets, seed in parallel with Task.WhenAll:

private async Task SeedManyAsync(Container container, int count)
{
    var tasks = Enumerable.Range(1, count).Select(i =>
    {
        var order = new Order
        {
            Id = $"order-{i}",
            CustomerId = $"cust-{i % 20}",
            Product = $"Product-{i}",
            Total = i * 1.5m
        };
        return container.UpsertItemAsync(order, new PartitionKey(order.CustomerId));
    });

    await Task.WhenAll(tasks);
}

Seeding from a File or Snapshot

Use state persistence to restore a pre-built dataset. This is ideal for large, stable seed datasets that don't change between tests, or for sharing data between repositories.

Export a Snapshot

Build the dataset once and export it to a JSON file:

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

// Build your data
await cosmos.Container.CreateItemAsync(new { id = "1", customerId = "c1", product = "Widget" }, new PartitionKey("c1"));
await cosmos.Container.CreateItemAsync(new { id = "2", customerId = "c2", product = "Gadget" }, new PartitionKey("c2"));
// ... more items

// Export to file
cosmos.ExportStateToFile("TestData/orders-seed.json");

Import at Test Time

Restore from a file during setup:

// Direct instantiation
using var cosmos = InMemoryCosmos.Create("orders", "/customerId");
cosmos.ImportStateFromFile("TestData/orders-seed.json");

// Via Dependency Injection callback
services.UseInMemoryCosmosContainers(options =>
{
    options.AddContainer("orders", "/customerId");
    options.OnContainerCreated = container =>
    {
        container.ImportStateFromFile("TestData/orders-seed.json");
    };
});

// Via OnHandlerCreated
services.UseInMemoryCosmosDB(options =>
{
    options.AddContainer("orders", "/customerId");
    options.OnHandlerCreated = (name, handler) =>
    {
        ((IContainerTestSetup)handler.BackingContainer).ImportStateFromFile("TestData/orders-seed.json");
    };
});

Import from a JSON String

If the seed data is embedded or generated programmatically:

var json = """
{
  "items": [
    { "id": "1", "customerId": "c1", "product": "Widget" },
    { "id": "2", "customerId": "c2", "product": "Gadget" }
  ]
}
""";

container.ImportState(json);

Sharing Snapshots Between Test Suites

Check seed files into source control so they're available everywhere:

tests/
  TestData/
    orders-seed.json
    customers-seed.json
    products-seed.json
  MyApp.Tests/
    OrderTests.cs

Set the files as Content / Copy if newer in your .csproj:

<ItemGroup>
  <Content Include="TestData\*.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Seeding with Direct Container Access

When you need to seed after the host is built but before tests run, capture a reference to the IContainerTestSetup or FakeCosmosHandler and seed directly.

Capture via OnHandlerCreated

public class OrderApiFactory : WebApplicationFactory<Program>
{
    public FakeCosmosHandler? OrdersHandler { get; private set; }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            services.UseInMemoryCosmosDB(options =>
            {
                options.AddContainer("orders", "/customerId");
                options.OnHandlerCreated = (name, handler) =>
                {
                    if (name == "orders")
                        OrdersHandler = handler;
                };
            });
        });
    }
}

public class OrderApiTests : IClassFixture<OrderApiFactory>
{
    private readonly OrderApiFactory _factory;

    public OrderApiTests(OrderApiFactory factory) => _factory = factory;

    [Fact]
    public async Task GetOrder_WithSeededData()
    {
        // Seed directly into the backing container
        var backing = _factory.OrdersHandler!.BackingContainer;
        await backing.CreateItemAsync(
            new { id = "order-1", customerId = "cust-1", total = 99.99 },
            new PartitionKey("cust-1"));

        var client = _factory.CreateClient();
        var response = await client.GetAsync("/api/orders/order-1");
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }
}

Capture via OnContainerCreated

public class OrderApiFactory : WebApplicationFactory<Program>
{
    public IContainerTestSetup? OrdersSetup { get; private set; }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            services.UseInMemoryCosmosContainers(options =>
            {
                options.AddContainer("orders", "/customerId");
                options.OnContainerCreated = setup =>
                {
                    OrdersSetup = setup;
                };
            });
        });
    }
}

Custom Factory Interface (Pattern 5)

When using a custom factory interface, expose state methods for seeding:

public class CosmosDbFixture : IDatabaseClientFactory, IDisposable
{
    private readonly InMemoryCosmosResult _cosmos;

    public CosmosDbFixture()
    {
        _cosmos = InMemoryCosmos.Builder()
            .AddContainer("orders", "/customerId")
            .AddContainer("customers", "/id")
            .Build();
    }

    public Container GetContainer(string containerName)
        => _cosmos.Containers[containerName];

    /// <summary>Direct access for registering sprocs, UDFs, triggers.</summary>
    public IContainerTestSetup SetupContainer(string containerName)
        => _cosmos.SetupContainer(containerName);

    /// <summary>Import state from a file for seeding.</summary>
    public void ImportStateFromFile(string filePath, string containerName)
        => _cosmos.ImportStateFromFile(filePath, containerName);

    public void Dispose() => _cosmos.Dispose();
}

// Seed in tests:
fixture.ImportStateFromFile("TestData/orders-seed.json", "orders");
// Or seed via the SDK Container:
await fixture.GetContainer("orders").CreateItemAsync(
    new Order { Id = "1", CustomerId = "cust-1" },
    new PartitionKey("cust-1"));

Persisting State Between Test Runs

The emulator can automatically save and restore container state between test runs. This is useful when you want data created during one test run to be available in the next, without manually calling ExportState/ImportState.

For the full guide — including Dependency Injection setup, direct instantiation, file naming conventions, and behaviour details — see State Persistence.


Resetting Data Between Tests

The container is shared across tests within the same factory or fixture. To avoid test pollution, clear data between tests.

ClearItems()

Removes all items, ETags, timestamps, and change feed entries:

public class OrderTests : IAsyncLifetime, IAsyncDisposable
{
    private readonly InMemoryCosmosResult _cosmos =
        InMemoryCosmos.Create("orders", "/customerId");

    public Task InitializeAsync() => Task.CompletedTask;

    public Task DisposeAsync()
    {
        _cosmos.ClearItems();
        return _cosmos.DisposeAsync().AsTask();
    }
}

Clear via Captured Reference (WebApplicationFactory)

public class OrderApiTests : IClassFixture<OrderApiFactory>, IAsyncLifetime
{
    private readonly OrderApiFactory _factory;

    public OrderApiTests(OrderApiFactory factory) => _factory = factory;

    public ValueTask InitializeAsync() => ValueTask.CompletedTask;

    public ValueTask DisposeAsync()
    {
        _factory.OrdersSetup?.ClearItems();
        return ValueTask.CompletedTask;
    }
}

Isolated Tests — New Setup Per Test

If test isolation matters more than speed, create a fresh setup for each test:

[Fact]
public async Task EachTest_GetsCleanState()
{
    using var cosmos = InMemoryCosmos.Create("orders", "/customerId");

    await cosmos.Container.CreateItemAsync(
        new { id = "1", customerId = "c1" },
        new PartitionKey("c1"));

    // cosmos.Container.ItemCount... verify via query
}

Choosing an Approach

Scenario Recommended approach
Integration tests with WebApplicationFactory Dependency Injection callbacks for shared data, SDK methods for per-test data
Unit tests with InMemoryCosmos SDK methods with a SeedAsync helper
Large, stable reference datasets State persistence with ImportStateFromFile
Data that evolves across test runs Automatic persistence with StatePersistenceDirectory
Performance / load tests Parallel bulk seeding or state persistence
Custom factory interface (Pattern 5/6) Direct container access via exposed ImportStateFromFile() / SetupContainer()
Test isolation required New setup per test or ClearItems() in teardown
Seeding across multiple containers Multiple container callbacks

See Also

Clone this wiki locally