Testing Reference
Test utilities for atom-based applications.
Overview
The atom/testing package provides utilities for testing code that uses atom.
import atomtest "github.com/zoobz-io/atom/testing"
AtomBuilder
Fluent builder for constructing test atoms.
Constructor
func NewAtomBuilder() *AtomBuilder
Methods
// Scalar types
func (b *AtomBuilder) String(key, value string) *AtomBuilder
func (b *AtomBuilder) Int(key string, value int64) *AtomBuilder
func (b *AtomBuilder) Uint(key string, value uint64) *AtomBuilder
func (b *AtomBuilder) Float(key string, value float64) *AtomBuilder
func (b *AtomBuilder) Bool(key string, value bool) *AtomBuilder
func (b *AtomBuilder) Time(key string, value time.Time) *AtomBuilder
func (b *AtomBuilder) Bytes(key string, value []byte) *AtomBuilder
// Pointer types
func (b *AtomBuilder) StringPtr(key string, value *string) *AtomBuilder
func (b *AtomBuilder) IntPtr(key string, value *int64) *AtomBuilder
// Slice types
func (b *AtomBuilder) StringSlice(key string, value []string) *AtomBuilder
func (b *AtomBuilder) IntSlice(key string, value []int64) *AtomBuilder
// Nested types
func (b *AtomBuilder) Nested(key string, value *atom.Atom) *AtomBuilder
func (b *AtomBuilder) NestedSlice(key string, value []atom.Atom) *AtomBuilder
// Metadata
func (b *AtomBuilder) WithSpec(spec atom.Spec) *AtomBuilder
func (b *AtomBuilder) Build() *atom.Atom
Example
a := atomtest.NewAtomBuilder().
String("Name", "Alice").
Int("Age", 30).
Bool("Active", true).
Build()
user, _ := atomizer.Deatomize(a)
AtomMatcher
Deep comparison utilities for atoms.
Functions
func Equal(a, b *atom.Atom) bool
func EqualFields(a, b *atom.Atom, fields ...string) bool
func Diff(a, b *atom.Atom) string
Example
func TestAtomization(t *testing.T) {
original := &User{Name: "Alice", Age: 30}
a := atomizer.Atomize(original)
expected := atomtest.NewAtomBuilder().
String("Name", "Alice").
Int("Age", 30).
Build()
if !atomtest.Equal(a, expected) {
t.Errorf("mismatch:\n%s", atomtest.Diff(a, expected))
}
}
RoundTripValidator
Automated round-trip testing.
Constructor
func NewRoundTripValidator[T any](atomizer *atom.Atomizer[T]) *RoundTripValidator[T]
Methods
func (v *RoundTripValidator[T]) Validate(t *testing.T, original *T)
func (v *RoundTripValidator[T]) ValidateAll(t *testing.T, cases []*T)
Example
func TestUserRoundTrip(t *testing.T) {
atomizer, _ := atom.Use[User]()
validator := atomtest.NewRoundTripValidator(atomizer)
cases := []*User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "", Age: 0}, // Edge case
}
validator.ValidateAll(t, cases)
}
FieldChecker
Type-safe field value assertions.
Functions
func GetString(a *atom.Atom, key string) (string, bool)
func GetInt(a *atom.Atom, key string) (int64, bool)
func GetUint(a *atom.Atom, key string) (uint64, bool)
func GetFloat(a *atom.Atom, key string) (float64, bool)
func GetBool(a *atom.Atom, key string) (bool, bool)
func GetTime(a *atom.Atom, key string) (time.Time, bool)
func GetBytes(a *atom.Atom, key string) ([]byte, bool)
Example
func TestUserFields(t *testing.T) {
a := atomizer.Atomize(&User{Name: "Alice", Age: 30})
name, ok := atomtest.GetString(a, "Name")
if !ok || name != "Alice" {
t.Errorf("expected Name=Alice, got %s", name)
}
age, ok := atomtest.GetInt(a, "Age")
if !ok || age != 30 {
t.Errorf("expected Age=30, got %d", age)
}
}
MustUse
Panics on registration failure (for test setup).
func MustUse[T any](t *testing.T) *atom.Atomizer[T]
Example
func TestUser(t *testing.T) {
atomizer := atomtest.MustUse[User](t)
// Test code...
}
TypeFixtures
Common test data generators.
Functions
func RandomString(length int) string
func RandomInt(min, max int64) int64
func RandomFloat(min, max float64) float64
func RandomBytes(length int) []byte
func RandomTime() time.Time
Example
func TestRandomUser(t *testing.T) {
user := &User{
Name: atomtest.RandomString(10),
Age: atomtest.RandomInt(18, 100),
Score: atomtest.RandomFloat(0, 100),
}
validator.Validate(t, user)
}
AssertAtom
Testing assertions for atoms.
Functions
func AssertHasString(t *testing.T, a *atom.Atom, key, expected string)
func AssertHasInt(t *testing.T, a *atom.Atom, key string, expected int64)
func AssertHasFloat(t *testing.T, a *atom.Atom, key string, expected float64)
func AssertHasBool(t *testing.T, a *atom.Atom, key string, expected bool)
func AssertHasNested(t *testing.T, a *atom.Atom, key string)
func AssertMissingField(t *testing.T, a *atom.Atom, table atom.Table, key string)
Example
func TestUserAtom(t *testing.T) {
a := atomizer.Atomize(&User{Name: "Alice", Age: 30})
atomtest.AssertHasString(t, a, "Name", "Alice")
atomtest.AssertHasInt(t, a, "Age", 30)
atomtest.AssertMissingField(t, a, atom.TableStrings, "NotAField")
}
Example Test File
package myapp
import (
"testing"
"github.com/zoobz-io/atom"
atomtest "github.com/zoobz-io/atom/testing"
)
func TestUserAtomization(t *testing.T) {
atomizer := atomtest.MustUse[User](t)
t.Run("basic round trip", func(t *testing.T) {
original := &User{Name: "Alice", Age: 30}
a := atomizer.Atomize(original)
atomtest.AssertHasString(t, a, "Name", "Alice")
atomtest.AssertHasInt(t, a, "Age", 30)
restored, err := atomizer.Deatomize(a)
if err != nil {
t.Fatal(err)
}
if restored.Name != original.Name {
t.Errorf("Name: got %q, want %q", restored.Name, original.Name)
}
})
t.Run("builder pattern", func(t *testing.T) {
a := atomtest.NewAtomBuilder().
String("Name", "Bob").
Int("Age", 25).
Build()
user, err := atomizer.Deatomize(a)
if err != nil {
t.Fatal(err)
}
if user.Name != "Bob" {
t.Errorf("expected Bob, got %s", user.Name)
}
})
t.Run("random data", func(t *testing.T) {
validator := atomtest.NewRoundTripValidator(atomizer)
for i := 0; i < 100; i++ {
user := &User{
Name: atomtest.RandomString(20),
Age: atomtest.RandomInt(0, 150),
}
validator.Validate(t, user)
}
})
}
Best Practices
Use MustUse in Tests
// Good - fails test on error
atomizer := atomtest.MustUse[User](t)
// Verbose - manual error handling
atomizer, err := atom.Use[User]()
if err != nil {
t.Fatalf("Use failed: %v", err)
}
Use Builders for Clarity
// Good - clear intent
a := atomtest.NewAtomBuilder().
String("Name", "Alice").
Int("Age", 30).
Build()
// Less clear
a := &atom.Atom{
Strings: map[string]string{"Name": "Alice"},
Ints: map[string]int64{"Age": 30},
}
Use Validators for Round-Trips
// Good - automated validation
validator.ValidateAll(t, testCases)
// Manual - error-prone
for _, tc := range testCases {
a := atomizer.Atomize(tc)
restored, _ := atomizer.Deatomize(a)
// Manual comparison...
}