Skip to Content
How-To GuidesGoUnit Testing a Go Golem Component

Unit Testing a Go Golem Component

A Go component’s packages build natively as well as for WebAssembly, so go test ./... runs ordinary Go tests against them on the development machine. There is no Golem host in a native test: test the definitions, the logic and the values, and leave what talks to Golem (RPC, durability, key-value, blobstore, agent management, tool calls) to the integration tests (see golem-integration-test-setup).

go test ./...

Check the definitions

Mistakes in agent and tool declarations — an unsupported type, a bad HTTP route, an unbound tool argument, a method handled twice — are reported by golem.DefinitionErrors(). Asserting it is empty catches them before golem build:

package counter import ( "testing" "github.com/golemcloud/golem/sdks/go/golem" ) func TestDefinitionsAreValid(t *testing.T) { for _, err := range golem.DefinitionErrors() { t.Error(err) } }

The check covers what the test binary registers, so it runs in the package that implements the agents (or one that imports it). An agent’s methods are the ones it handles: a method descriptor without a handler is not part of the agent and is not checked.

Test the logic

Handlers are closures registered in init; keep their logic in plain functions or methods on the state, and test those directly:

type State struct{ Total int64 } func (s *State) add(n int64) int64 { s.Total += max(n, 0) return s.Total } func init() { impl := Agent.Implement(func(ID) *State { return &State{} }) impl.Handle(Add, func(ctx *golem.Context[State], in AddIn) int64 { return ctx.State.add(in.N) }) }
func TestAdd(t *testing.T) { s := &State{} if got := s.add(3); got != 3 { t.Fatalf("add(3) = %d", got) } }

Test streams

golem.AgentStream runs natively as an in-memory pipe, so a producer and its consumer work in a test as they do between agents:

func countUp(total int64) golem.AgentStream[string] { return golem.ProduceStream(func(w *golem.AgentStreamWriter[string]) error { for i := int64(1); i <= total; i++ { if err := w.Write(fmt.Sprint(i)); err != nil { return err } } return nil }) } func TestCountUp(t *testing.T) { got := countUp(3).Collect() if len(got) != 3 || got[2] != "3" { t.Fatalf("stream gave %v", got) } }

golem.StreamOf(items...) makes a stream of fixed values to feed code that consumes one.

Round-trip values

golem.EncodeTypedValue and golem.DecodeTypedValue convert through the same schema a method’s parameters, results and snapshots use, which shows how a type travels:

func TestStateRoundTrips(t *testing.T) { v := golem.EncodeTypedValue(State{Total: 7}) back, err := golem.DecodeTypedValue[State](v) if err != nil || back.Total != 7 { t.Fatalf("round trip gave %+v, %v", back, err) } t.Log(v.JSON()) }
GuideDescription
golem-add-agent-goDeclaring agents, methods and their types
golem-define-tool-goDeclaring tools, whose mistakes DefinitionErrors also reports
golem-integration-test-setupTesting against a running Golem server
golem-test-crash-recoveryTesting durable behavior across crashes and restarts