The dump package is a utility that serializes any Go value into its string
representation.
The dump package is part of the CTX42 Testing Module, an ongoing effort to
build a new, flexible, and developer-friendly testing framework.
The dump package provides a configurable way to render any Go value — whether
it’s a simple integer, a nested struct, or a recursive data structure — into a
human-readable string. This is particularly useful in testing, where comparing
complex values often requires more than Go’s built-in reflect.DeepEqual. By
converting values to strings, the dump package lets you leverage string
comparison or diffing tools to pinpoint discrepancies quickly and accurately.
val := testcases.TA{
Dur: 3,
Int: 42,
Loc: testcases.WAW,
Str: "abc",
Tim: time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC),
TAp: nil,
}
have := dump.New().Any(val)
fmt.Println(have)
// Output:
// {
// Int: 42,
// Str: "abc",
// Tim: "2000-01-02T03:04:05Z",
// Dur: "3ns",
// Loc: "Europe/Warsaw",
// TAp: nil,
// private: 0,
// }The default dump renders the struct in a nicely formatted, multiline string. Fields are listed in the order they’re declared in the struct, ensuring consistent output for reliable comparisons.
One of the dump package’s strengths is its configurability. You can tweak how
values are rendered to suit your needs. Here are some key options:
For a compact, single-line representation, use the Flat option:
val := map[string]any{
"int": 42,
"loc": testcases.WAW,
"nil": nil,
}
have := dump.New(dump.WithFlat).Any(val)
fmt.Println(have)
// Output:
// map[string]any{"int": 42, "loc": "Europe/Warsaw", "nil": nil}For maps, keys are sorted (when possible) to maintain consistency.
You can customize how time.Time values are displayed using the
dump.WithTimeFormat option:
val := map[time.Time]int{time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC): 42}
have := dump.New(dump.WithFlat, dump.WithTimeFormat(time.Kitchen)).Any(val)
fmt.Println(have)
// Output:
// map[time.Time]int{"3:04AM": 42}By default, pointer addresses are hidden, but you can enable them with
dump.WithPtrAddr option:
val := map[string]any{
"fn0": func() {},
"fn1": func() {},
}
have := New(dump.WithPtrAddr).Any(val)
fmt.Println(have)
// Output:
// map[string]any{
// "fn0": <func>(<0x533760>),
// "fn1": <func>(<0x533780>),
// }For ultimate flexibility, you can define custom dumpers for specific types.
Dumpers for types are regular functions matching dump.Dumper signature declared in
the package.
type Dumper func(dmp Dump, level int, val reflect.Value) stringFor example:
var i int
dumper := func(dmp dump.Dump, lvl int, val reflect.Value) string {
switch val.Kind() {
case reflect.Int:
return fmt.Sprintf("%X", val.Int())
default:
panic("unexpected kind")
}
}
opts := []dump.Option{
dump.WithFlat,
dump.WithCompact,
dump.WithDumper(i, dumper),
}
have := dump.New(opts...).Any(42)
fmt.Println(have)
// Output:
// 2AThe above example dumps integers as hexadecimal values, showcasing how you can tailor the output for your use case.
The dump package shines when dealing with complicated or recursive data
structures. It includes cycle detection to prevent infinite loops. Here’s an
example with a recursive struct:
type Node struct {
Value int
Children []*Node
}
val := &Node{
Value: 1,
Children: []*Node{
{
Value: 2,
Children: nil,
},
{
Value: 3,
Children: []*Node{
{
Value: 4,
Children: nil,
},
},
},
},
}
have := dump.New().Any(val)
fmt.Println(have)
// Output:
// {
// Value: 1,
// Children: []*dump_test.Node{
// {
// Value: 2,
// Children: nil,
// },
// {
// Value: 3,
// Children: []*dump_test.Node{
// {
// Value: 4,
// Children: nil,
// },
// },
// },
// },
// }The Dump package is built with extensibility in mind. Custom dumpers let you define how your own types are serialized, integrating seamlessly with the framework. This adaptability ensures the package can grow with your project’s needs.
Testing equivalence of complex data structures in Go doesn’t have to be a chore.
The dump package simplifies the process by converting any value into a string,
ready for comparison or diffing. Its configurability and extensibility make it
a versatile tool for any testing scenario.