Testing Transports
transport/transporttest is the standard testing helper for transport authors. It exports ParseJSONLine for asserting on JSON output and RunContract for the wrapper-transport contract suite that every built-in wrapper passes.
Direct buffer assertions
Drive entries through your transport via a real *loglayer.LogLayer and assert on whatever your transport actually produced (a buffer, a captured request, a wrapped logger's calls). The pattern mirrors the built-in transport tests:
import (
"bytes"
"testing"
"go.loglayer.dev"
"go.loglayer.dev/transport"
"go.loglayer.dev/transport/transporttest"
)
func TestMyTransport_Basic(t *testing.T) {
buf := &bytes.Buffer{}
tr := mytransport.New(mytransport.Config{
BaseConfig: transport.BaseConfig{ID: "test"},
Writer: buf,
})
log := loglayer.New(loglayer.Config{
Transport: tr,
DisableFatalExit: true,
})
log.WithFields(loglayer.Fields{"k": "v"}).Info("served")
obj := transporttest.ParseJSONLine(t, buf)
if obj["k"] != "v" {
t.Errorf("k: got %v, want \"v\"", obj["k"])
}
}For wrapper transports (those that hand entries off to a third-party logger), assert on the wrapped logger's output rather than the transport's. The slog/zerolog/zap test files in transports/ show this pattern.
The wrapper contract suite
transport/transporttest ships a RunContract helper that drives 14 sub-tests against any wrapper-shaped transport (renders to a buffer in JSON-per-line). The same suite verifies every built-in wrapper. Wire it in with a Factory closure that builds a fresh (*loglayer.LogLayer, *bytes.Buffer) honoring per-test config overrides, plus an Expectations struct describing your wrapper's rendering quirks (message key, level rendering, fatal handling):
func factory(opts transporttest.FactoryOpts) (*loglayer.LogLayer, *bytes.Buffer) {
buf := &bytes.Buffer{}
cfg := mytransport.Config{
BaseConfig: transport.BaseConfig{ID: "mywrap", Level: opts.Level},
Logger: buildBackend(buf),
MetadataFieldName: opts.MetadataFieldName,
}
tr := mytransport.New(cfg)
return loglayer.New(loglayer.Config{DisableFatalExit: true, Transport: tr}), buf
}
func TestMyWrapperContract(t *testing.T) {
transporttest.RunContract(t, transporttest.ContractCase{
Name: "mywrap",
Factory: factory,
Expect: transporttest.Expectations{
MessageKey: "msg",
LevelKey: "level",
Levels: map[loglayer.LogLevel]string{
loglayer.LogLevelDebug: "debug",
loglayer.LogLevelInfo: "info",
loglayer.LogLevelWarn: "warn",
loglayer.LogLevelError: "error",
loglayer.LogLevelFatal: "fatal",
},
},
})
}Set Expectations.SkipFatal for libraries that unconditionally call os.Exit on Fatal (the phuslu wrapper does this). Library-specific behavior (positive WithContext forwarding for slog-style wrappers, etc.) goes in your own per-wrapper test functions next to the RunContract call.
Cover the level-filtering case, the MetadataFieldName non-map path, and WithContext propagation when applicable. The existing wrapper-transport test files are good templates: same structure, same assertion shape.
See Also
- Creating Transports, implementing the
Transportinterface from scratch.
