Proposal: Public a2atest package for SDK consumers
Motivation
The a2a-go SDK has excellent internal test infrastructure in internal/testutil/
and internal/testutil/testexecutor/. However, these packages are not accessible
to SDK consumers, which means everyone building A2A agents in Go must create
their own test mocks, in-process servers, and assertion helpers from scratch.
This is the single most common pain point reported by Go SDK adopters. The
Python SDK provides a2a.testing with mock servers and test clients. The JS SDK
has similar utilities. The Go SDK is the only major A2A SDK without public test
helpers.
Current internal test infrastructure
The SDK already has well-designed test utilities that cover the core testing needs:
| Internal package |
Contents |
Consumers |
internal/testutil |
TestTaskStore, TestPushConfigStore, TestPushSender, TestEventQueue, TestQueueManager, TestWorkQueue, test logger |
a2asrv handler tests, internal/taskexec tests |
internal/testutil/testexecutor |
TestAgentExecutor with control channels, event generators, functional constructors |
e2e tests, handler tests |
These utilities follow a consistent pattern: they embed the real implementation
and allow individual method overrides via function fields. This is a clean,
composable design that SDK consumers would benefit from directly.
Proposed public API
A new top-level a2atest package (following the convention of a2aclient,
a2asrv, a2aext):
package a2atest
// --- In-process test server ---
// Server is an in-process A2A server for integration testing.
// It starts an httptest.Server with JSON-RPC and REST handlers wired up.
type Server struct { /* ... */ }
// NewServer creates an in-process A2A test server with the given executor
// and options. The server is automatically cleaned up when the test finishes.
func NewServer(t testing.TB, executor a2asrv.AgentExecutor, opts ...ServerOption) *Server
// URL returns the base URL of the test server.
func (s *Server) URL() string
// Card returns the AgentCard for the test server.
func (s *Server) Card() *a2a.AgentCard
// Client returns a pre-configured client connected to the test server.
func (s *Server) Client(t testing.TB) *a2aclient.Client
// Handler returns the underlying RequestHandler for direct handler testing.
func (s *Server) Handler() a2asrv.RequestHandler
// ServerOption configures the test server.
type ServerOption func(*serverConfig)
// WithTaskStore configures the test server with a custom task store.
func WithTaskStore(store taskstore.Store) ServerOption
// WithCapabilities sets the agent capabilities on the test server's AgentCard.
func WithCapabilities(caps a2a.AgentCapabilities) ServerOption
// WithSkills sets the skills on the test server's AgentCard.
func WithSkills(skills []a2a.AgentSkill) ServerOption
// WithCallInterceptors adds call interceptors to the test server.
func WithCallInterceptors(interceptors ...a2asrv.CallInterceptor) ServerOption
// --- Mock executor ---
// Executor is a configurable mock AgentExecutor for testing.
// It embeds the internal TestAgentExecutor functionality.
type Executor struct { /* ... */ }
// NewExecutor creates a mock executor. Without configuration, it returns
// an empty event sequence.
func NewExecutor() *Executor
// FromFunc creates an Executor from a function.
func FromFunc(fn func(ctx context.Context, ec *a2asrv.ExecutorContext) iter.Seq2[a2a.Event, error]) *Executor
// FromEvents creates an Executor that emits a fixed sequence of events.
func FromEvents(fn func(execCtx *a2asrv.ExecutorContext) []a2a.Event) *Executor
// Emitted returns all events emitted by the executor (thread-safe).
func (e *Executor) Emitted() []a2a.Event
// WithControlChannels returns an Executor with channels for controlling
// execution timing in race condition tests.
func WithControlChannels() (*Executor, *ControlChannels)
// ControlChannels provides channels for controlling executor behavior.
type ControlChannels struct {
ReqCtx <-chan *a2asrv.ExecutorContext
ExecEvent chan<- a2a.Event
CancelCalled <-chan struct{}
ContinueCancel chan<- struct{}
}
// --- Mock task store ---
// TaskStore is a mock task store that delegates to InMemory by default.
// Individual operations can be overridden.
type TaskStore struct { /* ... */ }
// NewTaskStore creates a mock task store backed by in-memory storage.
func NewTaskStore() *TaskStore
// SetSaveError overrides Create and Update to return the given error.
func (s *TaskStore) SetSaveError(err error) *TaskStore
// SetGetOverride overrides Get to return the given task and error.
func (s *TaskStore) SetGetOverride(task *taskstore.StoredTask, err error) *TaskStore
// WithTasks seeds the store with the given tasks.
func (s *TaskStore) WithTasks(t testing.TB, tasks ...*a2a.Task) *TaskStore
// --- Test logger ---
// NewLogger returns an slog.Logger that directs output to t.Log().
// Output is only printed for failed tests or when running with -v.
func NewLogger(t testing.TB) *slog.Logger
// SetDefaultLogger calls slog.SetDefault with a test logger and restores
// the original logger on test cleanup.
func SetDefaultLogger(t testing.TB)
Design principles
- Export what already works: The internal test infrastructure is battle-tested.
The public API should be a thin wrapper, not a rewrite.
- Zero configuration for common cases:
a2atest.NewServer(t, executor) should
work with no options for the 80% case.
- Composable for advanced cases: Options for custom stores, interceptors,
and capabilities cover the remaining 20%.
- Test cleanup via
t.Cleanup(): All resources are automatically cleaned up
when the test finishes. No manual defer server.Close() needed.
- Follow the SDK's own patterns: Use
t.Helper(), cmp.Diff(),
t.Parallel(), and the same error formatting conventions documented in AGENTS.md.
Relationship to existing internal packages
The a2atest package would import from internal/testutil initially via
package-level delegation, then gradually replace the internal package as the
public API stabilizes. The internal package can eventually become a thin re-export
or be removed entirely once all internal tests migrate to the public API.
Alternatively, if the maintainers prefer, the internal utilities can be moved
directly to the public package in a single refactor. I am happy to go either
direction.
Implementation plan
I am happy to implement this. Proposed approach:
Phase 1 (1 PR): Create a2atest/ package with:
Server (in-process server with JSON-RPC handler)
Executor (wrapping testexecutor.TestAgentExecutor)
TaskStore (wrapping testutil.TestTaskStore)
NewLogger and SetDefaultLogger
- Tests and
doc.go with package documentation
- Example test showing the full pattern
Phase 2 (follow-up PR): Add:
- REST transport support in test server
- Assertion helpers (
AssertTaskState, AssertArtifacts, AssertEvents)
- Push notification test infrastructure (
PushConfigStore, PushSender)
- Migration of select internal tests to use the public API
Phase 3 (optional, based on feedback): Add:
- gRPC transport support in test server
- Test fixtures (pre-built AgentCards, common event sequences)
- Benchmarking helpers
Backward compatibility
This is a purely additive change. No existing APIs are modified. The new package
can be imported independently:
import "github.com/a2aproject/a2a-go/v2/a2atest"
Context
I have an open PR (#309) fixing a race condition in task execution cleanup, which gave me familiarity with the internal test infrastructure and motivated this proposal.
Questions for maintainers
- Does this align with your vision for the SDK's public API surface?
- Do you prefer the public package to delegate to
internal/testutil or to
directly move the internal code?
- Are there additional test utilities you've wished existed when writing the
SDK's own tests?
- Any naming preferences? (
a2atest vs testutil vs testing)
Proposal: Public
a2atestpackage for SDK consumersMotivation
The a2a-go SDK has excellent internal test infrastructure in
internal/testutil/and
internal/testutil/testexecutor/. However, these packages are not accessibleto SDK consumers, which means everyone building A2A agents in Go must create
their own test mocks, in-process servers, and assertion helpers from scratch.
This is the single most common pain point reported by Go SDK adopters. The
Python SDK provides
a2a.testingwith mock servers and test clients. The JS SDKhas similar utilities. The Go SDK is the only major A2A SDK without public test
helpers.
Current internal test infrastructure
The SDK already has well-designed test utilities that cover the core testing needs:
internal/testutilTestTaskStore,TestPushConfigStore,TestPushSender,TestEventQueue,TestQueueManager,TestWorkQueue, test loggera2asrvhandler tests,internal/taskexectestsinternal/testutil/testexecutorTestAgentExecutorwith control channels, event generators, functional constructorsThese utilities follow a consistent pattern: they embed the real implementation
and allow individual method overrides via function fields. This is a clean,
composable design that SDK consumers would benefit from directly.
Proposed public API
A new top-level
a2atestpackage (following the convention ofa2aclient,a2asrv,a2aext):Design principles
The public API should be a thin wrapper, not a rewrite.
a2atest.NewServer(t, executor)shouldwork with no options for the 80% case.
and capabilities cover the remaining 20%.
t.Cleanup(): All resources are automatically cleaned upwhen the test finishes. No manual
defer server.Close()needed.t.Helper(),cmp.Diff(),t.Parallel(), and the same error formatting conventions documented in AGENTS.md.Relationship to existing internal packages
The
a2atestpackage would import frominternal/testutilinitially viapackage-level delegation, then gradually replace the internal package as the
public API stabilizes. The internal package can eventually become a thin re-export
or be removed entirely once all internal tests migrate to the public API.
Alternatively, if the maintainers prefer, the internal utilities can be moved
directly to the public package in a single refactor. I am happy to go either
direction.
Implementation plan
I am happy to implement this. Proposed approach:
Phase 1 (1 PR): Create
a2atest/package with:Server(in-process server with JSON-RPC handler)Executor(wrappingtestexecutor.TestAgentExecutor)TaskStore(wrappingtestutil.TestTaskStore)NewLoggerandSetDefaultLoggerdoc.gowith package documentationPhase 2 (follow-up PR): Add:
AssertTaskState,AssertArtifacts,AssertEvents)PushConfigStore,PushSender)Phase 3 (optional, based on feedback): Add:
Backward compatibility
This is a purely additive change. No existing APIs are modified. The new package
can be imported independently:
Context
I have an open PR (#309) fixing a race condition in task execution cleanup, which gave me familiarity with the internal test infrastructure and motivated this proposal.
Questions for maintainers
internal/testutilor todirectly move the internal code?
SDK's own tests?
a2atestvstestutilvstesting)