Avanade

coreex-test-relay

"Understand, verify, or (rarely) extend an Outbox Relay host integration test in a CoreEx *.Test.Relay project. USE FOR: understanding the templated RelayTests.cs shape, verifying the relay forwards outbox events to the broker, hosted-service pause/resume endpoint checks, diagnosing Service Bus emulator entity-not-found failures. DO NOT USE FOR: API host tests (use coreex-test-api), Subscribe host tests (use coreex-test-subscribe), the relay host's Program.cs/hosted-service setup (see coreex-host-setup.instructions.md)."

Avanade 28 8 Updated 1w ago
GitHub

Install

npx skillscat add avanade/coreex/coreex-test-relay

Install via the SkillsCat registry.

SKILL.md

CoreEx: Outbox Relay Host Integration Test

Guides you through the *.Test.Relay project — the most templated and least-extended of the three
integration test projects. Unlike API/Subscribe tests, which grow one file per entity/subscriber as a
domain evolves, relay tests are largely generic plumbing checks generated by CoreEx.Template and
rarely need new scenarios per domain.

When to Use

  • Understanding what RelayTests.cs already verifies before assuming it needs extending
  • Verifying the relay forwards outbox-written events to the broker end-to-end
  • Adding a hosted-service pause/resume/status check
  • Diagnosing a Service Bus emulator "entity not found" test failure
  • The rare case where a domain has relay behavior beyond the generic forward-and-verify flow (e.g. a
    custom hosted-service management endpoint)

When Not to Use

Resolve project-wide choices from state before asking. Read the solution-root AGENTS.md
Feature Configuration: messaging-provider gates this skill (a Relay host exists only when a
messaging provider is configured) and data-provider (PostgreSQL vs SQL Server vs Cosmos) selects the outbox
publisher family (PostgresOutboxPublisher/SqlServerOutboxPublisher/CosmosDbEventPublisher) — never mix them. Only prompt
for what is unrecorded; re-state resolved values for confirmation.

Quick Reference

  • Base class: WithApiTester<{Domain}.Relay.Program> — relay hosts have no FusionCache; do not call ClearFusionCacheAsync() here
  • No business-data seeding needed for relational forwarding tests — write directly to the outbox via Test.ScopedType<ExecutionContext> and a provider-specific outbox publisher (PostgresOutboxPublisher/SqlServerOutboxPublisher). Cosmos requires provisioned business/lease containers and an outbox event paired with a business mutation in the same transaction.
  • Assert delivery via Test.GetAndClearAzureServiceBusAsync(...) against the expected topic/subscription — or, for a command, the target domain queue: Test.GetAndClearAzureServiceBusAsync(ServiceBusSessionReceiverOptions.CreateForQueue("{destination}-{target}")) (the queue must exist in Test.Common ServiceBus.GetQueues(); see coreex-command-publish-e2e)
  • Hosted-service management endpoints are also testable over plain HTTP — /hosted-services/{name}/pause, /resume, etc.
  • Check existing coverage before extending it — relational hosts usually need only RelayTests.cs + OtherTests.Health.cs + OtherTests.HostedServices.cs. Cosmos tests should cover every outbox-hosting container, cleanup, the first-start/checkpoint policy, and pause/resume.
public class RelayTests : WithApiTester<YourDomain.Relay.Program>
{
    [OneTimeSetUp]
    public async Task OneTimeSetUpAsync()
        => await Test.ResetAzureServiceBusAsync(Common.ServiceBus.GetQueues(), Common.ServiceBus.GetTopicsAndSubscriptions());   // Test.Common ServiceBus: the code-based topology.

    [Test]
    public async Task Outbox_Relay()
    {
        Test.ScopedType<ExecutionContext>(test =>
        {
            test.Run(async _ =>
            {
                var pub = ActivatorUtilities.GetServiceOrCreateInstance<PostgresOutboxPublisher>(test.Services);   // or SqlServerOutboxPublisher — provider-specific relational example
                pub.Add("{solution}", [ce1, ce2]);
                await pub.PublishAsync();

                for (int i = 0; i < 5; i++)
                    await Task.Delay(TimeSpan.FromSeconds(1));

                var list = await Test.GetAndClearAzureServiceBusAsync(
                    ServiceBusSessionReceiverOptions.CreateForTopicSubscription("{solution}", "{domain}"));

                list.Should().HaveCount(2);
            }).AssertSuccess();
        });
    }
}
Test.Http()
    .Run(HttpMethod.Post, "/hosted-services/postgres-outbox-relay-03/pause")
    .Response.StatusCode.Should().Be(HttpStatusCode.Accepted);

For Cosmos relay tests, publish via CosmosDbEventPublisher inside CosmosDbUnitOfWork.TransactionAsync(...), paired with a real business mutation, and use bounded polling to assert broker delivery and outbox cleanup. Keep the relay's default IEventPublisher registered as its broker destination. Provision containers before accessing the tester: even Test.Configuration constructs the host and captures its first-start boundary. Use a test-owned observation subscription and do not reset containers beneath a running change-feed processor. The Customers sample below demonstrates both PartitionKey.None and explicit partition keys.

Troubleshooting — Service Bus Emulator "Entity Not Found"

If a Relay (or any Service Bus) test fails with:

The messaging entity 'sb://sbemulatorns.servicebus.onebox.windows-int.net/<topic>/subscriptions/<subscription>' could not be found.

the test host reached the emulator but the topic/subscription doesn't exist in it. This is an
environment problem, not a test-code defect
— check that the Service Bus emulator container is
running, and that the test resets the entities via Test.ResetAzureServiceBusAsync(Common.ServiceBus.GetQueues(), Common.ServiceBus.GetTopicsAndSubscriptions())
(in a templated solution the emulator /servicebus/Config.json is intentionally empty; the entities are defined in the Test.Common ServiceBus class). Do not "fix" it by editing the test, subject names, or emulator entity names.

Key References