testutil

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: MIT Imports: 14 Imported by: 0

README

Package testutil

Package testutil provides utilities for testing code generation using golden files.

Overview

Golden file testing compares generated output against expected output stored in files. When code generation changes, you can review the differences and update the golden files if the changes are correct.

This package provides:

  • Simple assertion functions for common cases
  • A fluent API for advanced scenarios
  • Automatic formatting for Go code and JSON
  • Cross-platform line ending normalization
  • Batch operations for testing multiple files

Basic Usage

The simplest way to test generated code:

func TestCodeGen(t *testing.T) {
    // Generate your code
    code := generateSomeCode()
    
    // Compare with golden file
    testutil.AssertString(t, "testdata/golden/expected.golden", code)
}

Run tests normally:

go test

Update golden files when output changes:

go test -update

Common Patterns

Testing Multiple Files

When generating multiple related files:

func TestMultipleFiles(t *testing.T) {
    batch := testutil.NewBatch(t)
    
    batch.AddString("server.go.golden", generateServer()).
          AddString("client.go.golden", generateClient()).
          AddString("types.go.golden", generateTypes()).
          Compare()
}
Format-Specific Testing

The package automatically formats content based on file type:

func TestFormattedOutput(t *testing.T) {
    // Go code is automatically formatted
    goCode := generateGoCode() // even if unformatted
    testutil.AssertGo(t, "output.go.golden", goCode)
    
    // JSON is pretty-printed
    jsonData := generateJSON() // even if minified
    testutil.AssertJSON(t, "config.json.golden", jsonData)
}

Testing Multiple Generated Files

When testing code generators that produce multiple files, you can use batch operations to test them all at once. This ensures all generated files remain consistent with each other.

Advanced Usage

Fluent API

For more control over the comparison process:

func TestWithFluentAPI(t *testing.T) {
    gf := testutil.NewGoldenFile(t, "testdata/golden")
    
    code := generateCode()
    
    gf.StringContent(code).
       Path("service.go.golden").
       CompareContent()
}
Directory Comparison

Compare entire directory structures:

func TestGeneratedDirectory(t *testing.T) {
    // Generate files to a directory
    generateToDirectory("./generated")
    
    // Compare against golden directory
    snapshot := testutil.NewDirSnapshot(t, "./generated", "testdata/golden/expected")
    snapshot.Ignore("*.tmp", "*.log").Compare()
}

Command Line Flags

# Update golden files
go test -update    # or -u or -w

# Sequential updates (for debugging)
go test -update -golden.parallel=false

File Organization

Golden files are typically stored in testdata/golden/ directories. This is the default when using NewGoldenFile with an empty base path:

// Uses "testdata/golden" as base path
gf := testutil.NewGoldenFile(t, "")
gf.StringContent(code).Path("output.golden").CompareContent()
// Creates: testdata/golden/output.golden

// Uses custom base path
gf := testutil.NewGoldenFile(t, "testdata/custom")
gf.StringContent(code).Path("output.golden").CompareContent()
// Creates: testdata/custom/output.golden

// Assert functions use paths exactly as provided
testutil.AssertString(t, "testdata/golden/output.golden", code)
// Creates: testdata/golden/output.golden

Typical directory structure:

mypackage/
├── generator.go
├── generator_test.go
└── testdata/
    └── golden/
        ├── server.go.golden
        ├── client.go.golden
        └── types.go.golden

Content Type Detection

The package automatically detects and formats content based on file extensions:

  • .go or .go.golden: Formatted with go/format
  • .json or .json.golden: Pretty-printed with proper indentation
  • Other extensions: Treated as plain text
  • Other extensions: Treated as plain text

Notes

  • Golden files should be committed to version control
  • Always review diffs carefully before updating golden files
  • Line endings are automatically normalized for cross-platform compatibility
  • The package ensures thread-safe access to golden files

API Reference

See the package documentation for complete API details.

Documentation

Overview

Package testutil provides testing utilities for the Loom code generation framework.

Golden File Testing

The package provides utilities for golden file testing, a technique where expected outputs are stored in files and compared against actual outputs during tests. This is particularly useful for testing code generation where outputs can be large and complex.

Basic Usage:

func TestCodeGeneration(t *testing.T) {
	// Create a golden file manager
	gf := testutil.NewGoldenFile(t, "testdata/golden")

	// Generate code
	code := generateCode()

	// Compare with golden file
	gf.Compare(code, "mytest.golden")
}

Updating Golden Files:

To update golden files when the expected output changes, run tests with the -update flag:

go test ./... -update

Legacy Compatibility:

For backward compatibility with existing tests, use CompareOrUpdateGolden:

testutil.CompareOrUpdateGolden(t, actual, "path/to/file.golden")

This function uses the same -update flag but requires the full path to the golden file.

Advanced Usage:

The GoldenFile type provides additional methods for more complex scenarios:

// Compare multiple files at once
gf.CompareMultiple(map[string]string{
	"file1.golden": code1,
	"file2.golden": code2,
})

// Create golden file if it doesn't exist
gf.CompareOrCreate(code, "new.golden")

// Check if a golden file exists
if gf.Exists("optional.golden") {
	gf.Compare(code, "optional.golden")
}

Organization:

Golden files are typically organized under a testdata/golden directory within each package. This keeps test data close to the tests while maintaining a clean structure.

Package testutil provides world-class utilities for testing code generation with golden files. It offers a fluent API, intelligent diffing, batch operations, and format-aware comparisons.

Index

Constants

View Source
const DefaultBasePath = "testdata/golden"

DefaultBasePath is the default directory for golden files

Variables

This section is empty.

Functions

func Assert

func Assert(t testing.TB, goldenPath string, got []byte)

Assert provides a simple assertion API

func AssertGo

func AssertGo(t testing.TB, goldenPath string, got string)

AssertGo compares Go source code with proper formatting

func AssertJSON

func AssertJSON(t testing.TB, goldenPath string, got []byte)

AssertJSON compares JSON content with proper formatting

func AssertProjectionParity added in v1.0.13

func AssertProjectionParity(t testing.TB, expected, actual *expr.AttributeExpr)

AssertProjectionParity fails the test if the projection shape drifts.

func AssertProjectionViewParity added in v1.0.13

func AssertProjectionViewParity(t testing.TB, result *expr.ResultTypeExpr, view string, actual *expr.AttributeExpr)

AssertProjectionViewParity fails the test if actual does not contain every field required by the result type view.

func AssertString

func AssertString(t testing.TB, goldenPath string, got string)

AssertString provides a simple assertion API for strings

func CompareOrUpdateGolden

func CompareOrUpdateGolden(t *testing.T, actual, golden string)

CompareOrUpdateGolden provides a drop-in replacement for the legacy function used throughout the codebase. New code should use GoldenFile instead. The golden parameter should be a full path to the golden file.

func FormatProjectionParityDiffs added in v1.0.13

func FormatProjectionParityDiffs(diffs []ProjectionParityDiff) string

FormatProjectionParityDiffs formats parity diffs with stable ordering.

Types

type GoldenFile

type GoldenFile struct {
	// contains filtered or unexported fields
}

GoldenFile manages golden file testing operations with a fluent API

func NewGoldenFile

func NewGoldenFile(t testing.TB, basePath string) *GoldenFile

NewGoldenFile creates a new GoldenFile instance with default options

func (*GoldenFile) Compare deprecated

func (g *GoldenFile) Compare(actual string, golden string)

Compare compares the actual content with the golden file content (legacy API)

Deprecated: Use StringContent().Path().CompareContent() for the fluent API

func (*GoldenFile) CompareBytes

func (g *GoldenFile) CompareBytes(actual []byte, golden string)

CompareBytes is like Compare but works with byte slices (legacy API)

func (*GoldenFile) CompareContent

func (g *GoldenFile) CompareContent()

CompareContent performs the golden file comparison

func (*GoldenFile) Content

func (g *GoldenFile) Content(content []byte) *GoldenFile

Content sets the content to compare (fluent API)

func (*GoldenFile) Exists

func (g *GoldenFile) Exists(golden string) bool

Exists checks if a golden file exists

func (*GoldenFile) Path

func (g *GoldenFile) Path(path string) *GoldenFile

Path sets the golden file path (fluent API)

func (*GoldenFile) SetUpdateMode

func (g *GoldenFile) SetUpdateMode(update bool)

SetUpdateMode sets whether this GoldenFile should update golden files on compare.

func (*GoldenFile) StringContent

func (g *GoldenFile) StringContent(content string) *GoldenFile

StringContent sets string content to compare (fluent API)

type ProjectionParityDiff added in v1.0.13

type ProjectionParityDiff struct {
	Path string
	Want string
	Got  string
}

ProjectionParityDiff describes a projection drift found while comparing canonical and generated projection attributes.

func ProjectionParityDiffs added in v1.0.13

func ProjectionParityDiffs(expected, actual *expr.AttributeExpr) []ProjectionParityDiff

ProjectionParityDiffs compares expected and actual projection shapes.

func ProjectionViewParityDiffs added in v1.0.13

func ProjectionViewParityDiffs(result *expr.ResultTypeExpr, view string, actual *expr.AttributeExpr) []ProjectionParityDiff

ProjectionViewParityDiffs compares a result type view with a generated projected attribute. Extra fields in actual are ignored because generated projected types intentionally carry the union of all view fields while view-specific constructors and validators select the active subset.

type TestAPI added in v1.0.10

type TestAPI struct {
	// Name is the API name. Defaults to "test".
	Name string
	// Services enumerated in the API.
	Services []TestService
}

TestAPI describes a minimal design used to synthesize a DSL function for tests. It covers the common "one service, a handful of endpoints" shape used by most codegen test inputs. For cases that exercise transport- specific concerns (HTTP routes, gRPC rpc:tag, JSON-RPC method names) callers should still hand-write the DSL — this helper keeps the default cases terse and skippable.

Typical use:

fn := testutil.TestAPI{
    Name: "svc",
    Services: []testutil.TestService{{
        Name: "Foo",
        Methods: []testutil.TestMethod{{
            Name:    "GetUser",
            Payload: testutil.TestType{Name: "Request", Fields: []testutil.TestField{{Name: "id", Type: expr.String, Required: true}}},
            Result:  testutil.TestType{Name: "User"},
        }},
    }},
}.DSL()
root := codegen.RunDSL(t, fn)

func (TestAPI) DSL added in v1.0.10

func (a TestAPI) DSL() func()

DSL returns a func suitable for passing to eval.RunDSL (or one of the test-helper wrappers built on top of it). It closes over the TestAPI receiver, so multiple calls produce independent DSL funcs.

type TestField added in v1.0.10

type TestField struct {
	// Name is the attribute name.
	Name string
	// Type is the DataType of the attribute (e.g., expr.String, expr.Int).
	Type expr.DataType
	// Required reports whether the attribute should be marked Required.
	Required bool
	// Description is attached as the attribute description.
	Description string
}

TestField describes one attribute within a TestType.

type TestMethod added in v1.0.10

type TestMethod struct {
	// Name is the method name.
	Name string
	// Payload is the request shape. Leave zero for a method with no payload.
	Payload TestType
	// Result is the response shape. Leave zero for a method with no result.
	Result TestType
	// Errors are method-level error names.
	Errors []string
}

TestMethod describes one method within a TestService.

type TestService added in v1.0.10

type TestService struct {
	// Name is the service name.
	Name string
	// Methods enumerated on this service.
	Methods []TestMethod
	// Errors are service-level error names.
	Errors []string
}

TestService describes one service within a TestAPI.

type TestType added in v1.0.10

type TestType struct {
	// Name is the type name. Empty means no type.
	Name string
	// Fields enumerated on the type. May be empty for a scalar-like or empty
	// type (the DSL will emit an empty object).
	Fields []TestField
}

TestType describes a user-type used as a payload or result. The zero value means "no type".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL