| Project / directory | Scope |
|---|---|
Tests/ (conductor-csharp.test.csproj) |
Core SDK. |
Tests/ApiUnit/ |
Resource-API units, no server. |
Tests/Definition/ |
Workflow definition building. |
Tests/Worker/ |
Worker behaviour. |
Tests/Executor/ |
WorkflowExecutor. |
Tests/Telemetry/ |
Metrics collection. |
Tests/Integration/ |
End-to-end, against a live server. |
Tests/Api/ |
Resource APIs against a live server. |
Tests/TestData/, Tests/Helper/ |
Fixtures and helpers. |
Conductor.AI.Tests/ |
Agent unit tests. |
Conductor.AI.E2eTests/ |
Agent E2E suites, against a live server. |
Conductor.AI.{OpenAI,GoogleADK,SemanticKernel}.Tests/ |
Framework adapters. |
The split that matters: unit tests need no server, integration and E2E tests do. CI runs them as separate jobs for that reason.
A worker is a plain class — construct a Task, call Execute, assert on the
TaskResult. No server, no host:
var worker = new GreetWorker();
var task = new Task
{
InputData = new Dictionary<string, object> { ["name"] = "Conductor" }
};
var result = worker.Execute(task);
Assert.Equal("Hello, Conductor!", result.OutputData["greeting"]);This is the payoff of the stateless-worker design principle in workers.md — a worker with no workflow-specific state is trivially unit-testable.
ConductorWorkflow builds a definition in memory, so assertions can be made without
registering anything:
var workflow = new ConductorWorkflow().WithName("wf").WithVersion(1)
.WithTask(new SimpleTask("greet", "greet_ref"));
Assert.Equal("wf", workflow.Name);Every resource API has an interface (IWorkflowResourceApi, ITaskResourceApi, and so
on), so code that takes the interface can be tested against a mock rather than a server.
See api-map.md.
Tests/Integration/ runs against a real server. CI runs this as Run integration tests (v5) in pull_request.yml, with the server endpoint and auth key supplied as secrets.
Locally, point at a server and run:
export CONDUCTOR_SERVER_URL=http://localhost:8080/api
dotnet test Tests/conductor-csharp.test.csprojscripts/run-integration-oss.sh mirrors the integration_tests_oss job in
pull_request.yml: it starts a local Conductor OSS + Postgres stack (defined in
scripts/docker-compose-oss.yaml), waits for /health, runs the integration suite with
Orkes-only tests filtered out (ServerType!=Orkes), and tears the stack down on exit.
scripts/run-integration-oss.sh # default tag from scripts/docker-compose-oss.yaml
scripts/run-integration-oss.sh --version 3.33.0-rc1
scripts/run-integration-oss.sh --keep-up # leave the stack running afterwardsThe default tag is written in exactly one place — the image: line of
scripts/docker-compose-oss.yaml — and that is what both a plain local run and a fork-PR CI
run land on. A non-fork CI run overrides it with the E2E_TEST_OSS_CONDUCTOR_VERSION org
variable (currently latest), so CI and a local run are not necessarily on the same image;
pass --version to reproduce a specific CI run.
The script always prints the resolved conductoross/conductor tag and pulls it before
starting the stack, because none of the tags in play are immutable — latest plainly, and
rc tags get re-pushed. Without an explicit pull, docker compose up would silently reuse a
stale cached image instead of fetching the current one.
Tests tagged [Trait("ServerType", "Orkes")] are excluded from this run because they
exercise features OSS does not implement — everything in Tests/Integration/Orkes/, plus
EnvironmentVariableTests and
WorkflowLifecycleTests.UpdateWorkflowVariables_VariablesAreReflected. Why each one is
gated, and the OSS endpoint it needs, is recorded in a comment next to the trait in the test
file itself. Read that before adding or removing the trait, and confirm the change against a
freshly-pulled image — a test that fails against a stale local image may pass against
current OSS.
Conductor.AI.E2eTests/ is organised by feature, one numbered suite per area — basic
validation, tool calling, CLI tools, HTTP tools, stateful domains, PDF and media tools,
PLAN_EXECUTE refs, skills, agent client, auth headers, schedules, and SDK parity.
They are driven by .github/workflows/agent-e2e.yml, which downloads a server jar, starts
it, waits for /health, starts an MCP testkit, and runs the suites. That workflow also has
a Guard against vacuous run step asserting tests actually executed — worth knowing
about, because a suite that silently collects zero tests would otherwise look like a pass.
Extending the matching suite is usually the fastest way to reproduce a suspected SDK bug. See debugging.md.
Agent construction and serialization are testable offline. runtime.PlanAsync(agent)
requires a server (it compiles server-side), but AgentConfigSerializer output and
builder validation — for instance that Build() throws ConfigurationException when
sub-agents lack a strategy — do not.
CI produces a Cobertura report and uploads to Codecov. Unit tests in a unit-test project rather than among the integration suites keep that signal meaningful.