apitest: behavioural tests for Go HTTP services that render their own sequence diagrams
A simple and extensible behavioural testing library for Go. You can use api test to simplify REST API, HTTP handler and e2e tests.
At a glance
- What is it?
- apitest is a Go library for testing HTTP services the way a client uses them, with fluent builders for requests and expectations, declarative mocks for outbound HTTP calls, and a sequence diagram rendered for every test. It has no third party dependencies and works with any http.Handler or against a running server.
- Who is it for?
- apitest fits a Go team that wants black box tests against a handler or a live server, with the option to mock outbound calls and to get a sequence diagram when a test fails and you cannot see the interaction. It does not fit a team that wants white box tests of internal functions, or a service whose real behaviour depends on state you cannot seed.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The request builder and the expectation builder are one chain
apitest is a behavioural testing library for Go HTTP services. The idea it names is that in behavioural tests the internal structure of the app is not known by the tests: data goes in and the outputs are expected to meet certain conditions.
The API is a single fluent chain, and the quick start shows the whole shape of it:
func TestGetUser(t *testing.T) {
apitest.New().
Handler(handler).
Get("/user/1234").
Expect(t).
Status(http.StatusOK).
Body(`{"id": "1234", "name": "Tate"}`).
End()
}Everything before Expect(t) describes the request and everything after it describes the expected response, and End() runs the test. The Handler method takes any http.Handler, so a http.ServeMux, a gin engine or an echo instance all work, and the library also works against a running server.
The body comparison is worth noting because it is the difference between a test that passes for the right reason and one that passes by luck. When the expected body is valid JSON it is compared structurally, so key order and whitespace do not matter. Any other body is compared as an exact string, with Bodyf for a formatted expected body and BodyFromFile to read the expectation from disk.
Installation is a single go get of github.com/steinfletcher/apitest, and the API reference lives on pkg.go.dev with longer narrative documentation at apitest.dev.
Every part of the request has a named builder method
The request side covers the parts of an HTTP request that actually vary between endpoints, and each has a method rather than a configuration struct.
Methods and URLs are set with Get, Post, Put, Patch and Delete, each of which has an f variant that formats the URL, and Method covers anything else such as OPTIONS. A ready-made *http.Request can be supplied instead of the builder, using httptest.NewRequest and then HttpRequest on the chain.
Headers have Header for one, Headers for a map, and ContentType for the content type header alone. Query parameters have Query, QueryParams and QueryCollection, and QueryCollection sets repeated parameters so a map of string slices such as map[string][]string{"a": {"b", "c", "d"}} is encoded as a=b&a=c&a=d.
Cookies are built with Cookie and Cookies, where a cookie is created by apitest.NewCookie and given a value and a path.
Bodies come in several shapes. Body sets a raw body. JSON sets the body and the Content-Type header and accepts a string, a []byte, or any value that can be marshalled. BodyFromFile and JSONFromFile read from disk. FormData sends an application/x-www-form-urlencoded body, with FormData taking a key and a value or several values, while MultipartFormData and MultipartFile send multipart/form-data. The two form styles cannot be combined in one request, which is a real constraint rather than a documentation nicety.
Files are read from the OS by default, and UseFS swaps in any fs.FS such as an in-memory fstest.MapFS, so an upload test does not need a file on disk.
There are also builders for BasicAuth, for a request context through WithContext, and for GraphQL through GraphQLQuery or GraphQLRequest when an operation name is needed. The context builder is how deadlines, cancellation and context values reach the handler. And Intercept receives the built *http.Request just before it is sent, which is the escape hatch for changes the builder does not cover.
Mocks and the sequence diagram
Two features distinguish apitest from a plain request helper. The first is declarative mocking of outbound HTTP calls, with matchers for every part of the request, so a handler that calls a third-party service can be tested without that service running.
The second is the sequence diagram. The feature list puts it as sequence diagrams of each test, including mocked calls and database queries. That is the piece with real diagnostic value: when a test fails and the interaction between your handler, its outbound calls and its database is hard to reason about from the assertion alone, a diagram of what actually happened is the fastest way to see the ordering problem.
The examples directory shows how much work the database part involves. There are separate example projects for sequence diagrams with a MySQL database, with a PostgreSQL database and with a SQLite database, alongside a plain sequence-diagrams example and a mocks example. Three database engines each with their own example is a strong signal that the diagram feature records database queries too, not just HTTP calls.
The topic tags list jsonpath and mocking among others, and the top-level source files reflect the feature set: mocks.go and a mocks directory, matchers.go for the request matchers, diagram.go and its test, diff.go with a difflib directory for the body comparison, transport.go for the transport layer, and template.go. The library also works with websockets, which has its own example project.
Assertion libraries and the claim of no dependencies
apitest states that it has no third party dependencies and integrates with testing, Ginkgo and custom assertion libraries. There is a dedicated examples/ginkgo project for the Ginkgo case.
The go.mod is worth reading against that claim. The module declares Go 1.23 and requires github.com/davecgh/go-spew v1.1.1, which is the pretty printer used by the testing package to render values. So the dependency list in the manifest is not empty, but it is a single testing-time helper rather than a framework, and the project is not vendoring a mock library or an assertion library of its own.
The Makefile shows the development workflow. The test target first checks that go fmt produces no changes, then runs go vet, then runs the tests with the race detector and atomic coverage mode, writing a coverage profile. Linting is golangci-lint, with a .golangci.yml at the top of the repository, and the docs target runs a Hugo server from a docs directory.
The project states that the API is stable, that the library is maintained and issues are addressed, and that feature requests are considered. The release history supports that: v1.6.2 was published on 2026-10-19, v1.6.1 on 2026-09-03 and v1.6.0 on 2025-03-05, with the last push to the repository on 2026-10-19. The library is MIT licensed, and the README points to a Slack channel for discussion.
Framework coverage in the examples folder
The examples directory is the best catalogue of what apitest is tested against. There are projects for echo, fiber, gin, gorilla, httprouter and iris, which covers the common Go web frameworks and their routers. There is a ginkgo example for the BDD style runner, a graphql example, a mocks example, a websockets example, and the three database-backed sequence diagram examples.
That spread matters because the library's central claim is that it works with anything that implements http.Handler, and each framework project is a test of that claim in practice rather than in the abstract. A gin engine, an echo instance and a gorilla mux all satisfy the interface, so the difference between them is in middleware, routing and error handling rather than in the test itself.
The library also allows running against a running server rather than a handler, which the feature list names explicitly. That is the mode to use for end to end tests, where the process is already up and the test needs to exercise the real listener rather than a handler mounted in memory.
The distinction between the two modes is not cosmetic. Handler mode runs in the same process, which makes it fast and lets the library see database queries for the diagram, but it also means the test shares the process state of the application. Server mode goes over the network to a real deployment, which is closer to what a client does, and which is the mode the description of the project mentions alongside REST API, HTTP handler and end to end tests.
Where apitest is the wrong choice
The first thing to be clear about is that apitest is a black box tool. It knows about requests, responses, outbound calls and the sequence of interactions, and nothing about your functions. If the behaviour you want to pin down lives inside a domain service rather than behind an HTTP boundary, a unit test of that service is shorter, faster and more precise, and apitest will not help.
The second limitation is the diagram feature's scope. It renders mocked calls and database queries, which means it is drawing on instrumentation of the database driver as well as the HTTP transport. The presence of three separate database examples suggests this is well-trodden for MySQL, PostgreSQL and SQLite, but a project on a different driver, or one that reaches its data through an ORM with its own connection handling, would need to check that the query capture actually fires.
The third is the trade the library itself makes with black box testing. Because the internal structure is unknown to the tests, a failure gives you a wrong response rather than a stack frame in the function that caused it. The sequence diagram is the compensation for that, and it is a good one, but it is a diagnostic aid rather than a substitute for unit tests when the bug is in the logic rather than in the wiring.
The practical alternative is the httptest package from the standard library, which the library is built on top of, combined with a mocking library of your choosing. If your needs are a couple of status code checks, that route is less machinery. If you want request builders, JSON path matching, outbound mocks and a rendered diagram per test, apitest is a reasonable amount of code to not write yourself, and the fact that it works with a running server as well as a handler makes it usable as the outer layer of a test suite rather than only its inner one.
Editorial conclusion
apitest fits a Go team that wants black box tests against a handler or a live server, with the option to mock outbound calls and to get a sequence diagram when a test fails and you cannot see the interaction. It does not fit a team that wants white box tests of internal functions, or a service whose real behaviour depends on state you cannot seed. Before adopting it, check the version you pin against your Go version, since the module targets Go 1.23, and decide whether you will mock at the transport layer or against a running server, because the mocking and sequence diagram features are described for the handler path and the examples directory shows separate setups for database backed diagrams.
Frequently asked questions
How do I install and start using apitest?
Install it with `go get github.com/steinfletcher/apitest`, then build a request with the fluent chain, attach your handler, call Expect(t) and describe the expected response before ending with End(). The module targets Go 1.23.
Does apitest work with any Go web framework?
Yes. The Handler method takes any http.Handler, so a http.ServeMux, a gin engine or an echo instance all work, and the library can also run against a running server. The examples directory includes projects for echo, fiber, gin, gorilla, httprouter and iris.
How are response bodies compared?
A JSON body is compared structurally, so key order and whitespace do not matter, while any other body is compared as an exact string. Bodyf formats the expected body and BodyFromFile reads it from disk.
What can the sequence diagrams show?
Each test can render a sequence diagram including mocked calls and database queries. The examples directory has separate projects for sequence diagrams with MySQL, PostgreSQL and SQLite databases, alongside a plain sequence diagram example.
How does apitest handle outbound HTTP calls and file uploads?
Outbound calls can be mocked declaratively with matchers for every part of the request. For uploads, MultipartFormData and MultipartFile send multipart/form-data, files are read from the OS by default, and UseFS swaps in any fs.FS such as an in-memory fstest.MapFS.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/steinfletcher-apitest)Was this useful?