apitest
Reports

Custom reports

Write your own report formatter, and add your own events to diagrams.

Report formatters

Report accepts anything that implements apitest.ReportFormatter:

apitest.ReportFormatter
type ReportFormatter interface {
	Format(*apitest.Recorder)
}

Format is called once the test has run, with a Recorder that holds everything that happened. This formatter writes a short Markdown summary:

// markdownFormatter writes a short Markdown summary of each test.
type markdownFormatter struct {
	out *strings.Builder
}

func (f markdownFormatter) Format(r *apitest.Recorder) {
	fmt.Fprintf(f.out, "## %s\n\n%s\n\n", r.Title, r.SubTitle)

	for i, event := range r.Events {
		switch e := event.(type) {
		case apitest.HttpRequest:
			fmt.Fprintf(f.out, "%d. %s → %s: `%s %s`\n", i+1, e.Source, e.Target, e.Value.Method, e.Value.URL)
		case apitest.HttpResponse:
			fmt.Fprintf(f.out, "%d. %s → %s: `%d`\n", i+1, e.Source, e.Target, e.Value.StatusCode)
		case apitest.MessageRequest:
			fmt.Fprintf(f.out, "%d. %s → %s: %s\n", i+1, e.Source, e.Target, e.Header)
		case apitest.MessageResponse:
			fmt.Fprintf(f.out, "%d. %s → %s: %s\n", i+1, e.Source, e.Target, e.Header)
		}
	}

	fmt.Fprintf(f.out, "\nstatus %v in %v\n", r.Meta["status_code"], time.Duration(r.Meta["duration"].(int64)))
}

func TestReport_CustomFormatter(t *testing.T) {
	var out strings.Builder

	apitest.New("gets a user").
		Report(markdownFormatter{out: &out}).
		Mocks(getUserMock, getPreferencesMock).
		Handler(newUserAPI()).
		Get("/user/1234").
		Expect(t).
		Status(http.StatusOK).
		End()

	t.Log(out.String())
}
Output
## GET /user/1234

gets a user

1. cli → sut: `GET /user/1234`
2. sut → users: `GET http://users/api/user/1234`
3. users → sut: `200`
4. sut → preferences: `GET http://preferences/api/preferences/1234`
5. preferences → sut: `200`
6. sut → cli: `200`

status 200 in 504.791µs

The recorder

apitest.Recorder
type Recorder struct {
	Title    string         // the method and URL of the request
	SubTitle string         // the name passed to apitest.New
	Meta     map[string]any // see below
	Events   []Event        // in the order they happened
}

Each event is one of four types, which all have Source, Target and Timestamp fields:

EventRecordsPayload
apitest.HttpRequestThe request into the handler, and each request to a mock.Value *http.Request
apitest.HttpResponseEach response from a mock, and the final response.Value *http.Response
apitest.MessageRequestA custom event, such as a SQL query.Header, Body strings
apitest.MessageResponseA custom response, such as a SQL result.Header, Body strings

Meta holds the values passed to Meta, plus these keys that apitest adds:

KeyValue
nameThe test name.
method, pathThe method and URL of the request.
status_codeThe final status code.
durationHow long the test took, in nanoseconds, as an int64.
hashA stable identifier for the test, used to name report files.

Custom events

Code running inside the test can add its own events, which appear in every report including sequence diagrams. Create a recorder, give it to the code that should record, and pass the same recorder to the test with Recorder:

// Queue publishes messages and records each one on the test recorder.
type Queue struct {
	recorder *apitest.Recorder
}

func (q *Queue) Publish(topic, message string) {
	// ... publish to the real broker ...

	if q.recorder != nil {
		q.recorder.AddMessageRequest(apitest.MessageRequest{
			Source:    apitest.SystemUnderTestDefaultName,
			Target:    "kafka",
			Header:    "publish " + topic,
			Body:      message,
			Timestamp: time.Now().UTC(),
		})
	}
}

func TestCreateOrder_PublishesEvent(t *testing.T) {
	recorder := apitest.NewTestRecorder()
	queue := &Queue{recorder: recorder}

	apitest.New("creates an order").
		Recorder(recorder).
		Report(apitest.SequenceDiagram()).
		Handler(newOrderAPI(queue)).
		Post("/orders").
		JSON(`{"sku": "gopher-plush", "quantity": 1}`).
		Expect(t).
		Status(http.StatusAccepted).
		End()
}
An apitest report for POST /orders, with a publish event sent to kafka in the diagram and the list of messages
Open the live report

The x/db package records SQL queries in exactly this way. Other uses include message queues, caches, and SDK calls that do not go over HTTP.

Companion formatters

apitest-plantuml formats reports as PlantUML markup.

On this page