Fiber — Testing#
Test metrics#
- Test files: 95
*_test.gofiles - Source files (non-test): 148
.gofiles - Ratio (test / source): ~0.64
- Total test functions: 1,605 (
func Test*) - Benchmark functions: 348 (
func Benchmark*) - Fuzz functions: 1 (
FuzzUtilsGetOffer) t.Parallel()calls: 2,119 — virtually every test is parallel- Test frameworks:
testify/require(primary),testify/assert(secondary), stdlibtesting - No gomock, ginkgo, goconvey, or other third-party test frameworks
Test organization#
Placement#
Predominantly same package (internal white-box tests). The package breakdown:
| Package | Test files |
|---|---|
binder | 12 |
fiber (core) | 9 |
client | 9 |
session | 6 |
cors, csrf, cache, limiter, … | 2–4 each |
Only 3 test files use the external _test package suffix — the most notable being app_integration_test.go (package fiber_test), which tests cross-cutting concerns from the consumer’s perspective.
Helper packages#
None in the traditional sense — no testutil/, mocks/, or fakes/ directory. Instead, Fiber relies on three mechanisms:
app.Test(req *http.Request)— the built-in integration test helper on theApptype itself (see below)fasthttputil.NewInmemoryListener()— borrowed from fasthttp’s test utilities to spin up a real in-memory TCP listener for end-to-end scenarios- Inline test helpers defined at the top of large test files (
testStatus200,testErrorResponse,performOversizedRequest) and tagged witht.Helper()
Fixtures#
.github/testdata2/ and .github/testdata3/ contain Go template fixtures (*.tmpl) used by view-engine tests. No testdata/ directory at the repo root. No generated test fixtures or embedded test assets beyond templates.
Test patterns#
Table-driven tests#
- Prevalence: Heavy — 293 hits of
testCases/tt.Run/tc.namepatterns across test files - Style: Anonymous struct slices; each struct has a
name stringfield plus inputs and expected outputs. Sub-tests run viat.Run(tt.name, func(t *testing.T) { ... }). - Dedicated fixture file:
path_testcases_test.goseparates the router-path test matrix (hundreds of URL/param/match combinations) from test logic inpath_test.go. This prevents large test files from becoming hard to navigate. - Example:
bind_test.go:69—Test_BindError_ErrorFormatusest.Run("with field", ...)andt.Run("without field", ...)sub-tests witht.Parallel()inside each.
app.Test() — Fiber’s killer test primitive#
The most architecturally significant testing feature. app.Test(req *http.Request, config ...TestConfig) (app.go:1199) works by:
- Dumping the
*http.Requestto raw bytes viahttputil.DumpRequest - Writing those bytes into a
testConn(an in-memorynet.Connbacked by twobytes.Buffers) - Serving the fake connection through the real fasthttp server pipeline
- Parsing the response bytes back into
*http.Response
This lets test code use the standard net/http/httptest.NewRequest API while exercising the full fasthttp request/response cycle — no test server port needed, no goroutine leak risk, deterministic. It accounts for 1,875 usages across test files — essentially every unit test in every middleware package uses this pattern.
// Typical middleware test
app := fiber.New()
app.Use(New(Config{AllowOrigins: []string{"https://example.com"}}))
app.Get("/", func(c fiber.Ctx) error { return c.SendStatus(200) })
req := httptest.NewRequest(fiber.MethodGet, "/", http.NoBody)
req.Header.Set("Origin", "https://example.com")
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)Mocking approach#
- Strategy: No mocking framework. Dependencies are small, concrete, and passed directly. The fasthttp
RequestCtxis the main “seam” — tests construct one directly (&fasthttp.RequestCtx{}) and acquire aCtxwithapp.AcquireCtx(...). - Interface fakes:
app_test.godefines concretefileViewstruct implementing theViewsinterface to test template rendering without a real filesystem renderer. - Custom context fakes:
app_integration_test.go:35definesintegrationCustomCtxembedding*fiber.DefaultCtxto test the custom-context dispatch path (nextCustomvsnextin the router).
Integration tests#
- Present: Yes —
app_integration_test.go(packagefiber_test) - How: Uses
fasthttputil.NewInmemoryListener()to bind the server to an in-memory socket, then exercises it with a realfasthttp.Clientdialing the in-memory listener. The server lifecycle is managed viat.Cleanup(shutdown + error drain). - Scope: Tests middleware combination scenarios — how CORS, CSRF, session, cache, basicauth, etc. behave together when the error handler fires. A large
middlewareCombinationTestCasetable (withname,setup,configureRequest,handler,assertions,expectedStatusfields) drives each scenario. - Separation: A single dedicated file named
app_integration_test.goin the root package. No build tags — runs in the defaultgo test ./...invocation.
Fuzz tests#
- Present: 1 fuzz target —
FuzzUtilsGetOfferinhelpers_fuzz_test.go - Guarded by build constraint:
//go:build go1.18 - Target: The content-negotiation
getOffer()function — a parsing-heavy function that’s a natural candidate for fuzz-driven crashes.
Benchmarks#
- 348 benchmark functions scattered across core and middleware test files
- A dedicated
client/request_bench_test.gofocuses on HTTP client throughput - All benchmarks are co-located with the package they measure (no separate
bench/directory)
CI configuration#
From .github/workflows/test.yml:
jobs:
unit:
strategy:
matrix:
go-version: [1.25.x, 1.26.x]
platform: [ubuntu-latest, windows-latest, macos-latest]
steps:
- run: go run gotest.tools/gotestsum@latest -f testname --
./... -race -count=1 -coverprofile=coverage.txt
-covermode=atomic -shuffle=on
repeated:
runs-on: ubuntu-latest
steps:
- run: go run gotest.tools/gotestsum@latest -f testname --
./... -race -count=15 -shuffle=onKey CI choices:
-raceon every run — data race detection is not optional-shuffle=on— randomizes test execution order to surface order dependencies-count=15in therepeatedjob — runs the full suite 15 times to catch flaky tests (a rare CI discipline)gotestsumfor human-readable output- 6-platform matrix (2 Go versions × 3 OS)
- Codecov coverage tracking with
threshold: 0.5%regression gate; generated files (*_gen.go,*_msgp.go) explicitly excluded
Test quality observations#
What’s done well#
app.Test()is a best-in-class test primitive. It eliminates the need for a port, provides deterministic behavior, and speaks*http.Request/*http.Responseso test helpers from the stdlib work. Nearly every middleware author in the Go ecosystem deals with this problem ad hoc; Fiber solved it structurally.- Parallel by default. 2,119
t.Parallel()calls — the team treats parallel tests as the norm, not an optimization. Combined with-racein CI, this surfaces data races during development. -count=15repeated job is an unusually high bar for flakiness detection. Most projects use-count=1or-count=3. Running 15 iterations per CI push signals that the team has been burned by flaky tests and responded with infrastructure rather than acceptance.- Dedicated fixture file for large data tables (
path_testcases_test.go) keeps test logic readable without sacrificing coverage breadth. t.Helper()discipline — helper functions are consistently marked so failure output points to the call site, not the helper.- Integration test separation by file name and package suffix (
fiber_test) rather than build tags — a pragmatic choice that keeps the integration tests part of the default run without adding tag ceremony.
What could improve#
- Single fuzz target. The content-negotiation and routing code are complex string-parsing systems with security implications. A handful of additional fuzz targets (route matching, header parsing, cookie parsing) would be warranted given the project’s scale and HTTP-security exposure.
- No
testdata/directory. Template fixtures live in.github/testdata2/and.github/testdata3/, which is an unusual location that breaks the convention readers expect. - Coverage ignores
internal/(codecov.yml) — the internal storage packages have no stated coverage target.
Patterns worth emulating (for the book)#
app.Test()pattern — building a zero-dependency, in-process test helper directly into the framework is something every Go library could offer. Shows hownet.Connandhttputil.DumpRequestcan replace a live server for unit tests.-count=15flakiness job — separating “does it work” (count=1, 6-platform matrix) from “is it stable” (count=15, single platform) is a mature CI decomposition strategy.- Table-driven test data in its own file —
path_testcases_test.godemonstrates that when test data grows beyond ~200 lines, externalizing it to a dedicated file (stillpackage fiber, not JSON/YAML) keeps both the data and the test logic readable. t.Parallel()everywhere +-shuffle=on— the combination forces correct test isolation from day one and makes flakiness visible at PR time rather than production.