Skip to content

Report to Allure

Go Mokkit’s stages can be observed: an observer hears each stage entered, every step that ran — with its phase, verb name, duration and outcome — and the stage closing with the test’s verdict. The step names arrive exactly as the suite spelled them (arrange: UserExists[Buyer]), which is precisely what a test report wants to show.

report/allure is that observer, writing Allure 2 result files. It lives in the core module and depends on nothing but the standard library — allure-results is just JSON.

import "github.com/GrafGenerator/go-mokkit/report/allure"
func TestMain(m *testing.M) {
setup, err := mokkit.NewSetup(context.Background(), mocks, app)
if err != nil {
panic(err)
}
var reporter *allure.Reporter
if dir := os.Getenv("ALLURE_OUTPUT_PATH"); dir != "" {
if reporter, err = allure.New(dir, allure.WithSuite("clients e2e")); err != nil {
panic(err)
}
setup.Observe(reporter)
}
code := m.Run()
// An observer never fails a test; an incomplete report is surfaced here.
if reporter != nil {
if err := reporter.Err(); err != nil {
fmt.Fprintf(os.Stderr, "allure report incomplete: %v\n", err)
}
}
os.Exit(code)
}

Run with ALLURE_OUTPUT_PATH=./allure-results go test ./... and feed the directory to the Allure CLI or TestOps as usual.

One file per test, whose steps are the test’s own vocabulary, with real timings:

{
"name": "TestCreateClient_PublishesTheCreatedEvent",
"status": "passed",
"steps": [
{ "name": "arrange: NewClient[Client]", "status": "passed" },
{ "name": "arrange: CacheHasClient[Client]", "status": "passed" },
{ "name": "act: CreateClient[Client]", "status": "passed" },
{ "name": "inspect: EventPublished", "status": "passed" }
]
}

A step that failed carries its message and trace; a step that panicked is reported as broken rather than failed. historyId is stable across runs — it hashes the suite and test name — so TestOps can track history and retries.

allure is ~300 lines over a public seam you can point anywhere else — OpenTelemetry spans, a JUnit writer, a metrics counter:

type Observer interface {
StageEntered(test, stageID string)
StepRan(event StepEvent) // test, stage, phase, step, started, duration, err
StageClosed(test, stageID string, failed bool)
}

Register any number with setup.Observe(...). Two contract points worth knowing: observers must be safe for concurrent use (All branches and parallel tests both emit), and an observer has no way to fail a test — reporting problems are yours to surface, as Reporter.Err() does above.