testutil

package
v0.2.3-alpha Latest Latest
Warning

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

Go to latest
Published: Apr 10, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package testutil provides shared test utilities and helpers for javinizer-go tests.

This file contains test data builders for domain models using the builder pattern. Builders provide sensible defaults and fluent API methods to minimize test boilerplate while maintaining flexibility for custom test scenarios.

Package testutil provides shared test utilities and helpers for javinizer-go tests.

Package testutil provides shared test utilities and helpers for javinizer-go tests.

This file contains a standardized table-driven test template that serves as the canonical pattern for writing tests across all packages in javinizer-go.

Table-Driven Test Template

Table-driven tests are the preferred pattern when testing a function with multiple input scenarios. They provide consistency, readability, and easy test case addition.

When to Use Table-Driven Tests

  • Testing a function with 3+ different input scenarios
  • Validating error handling with multiple failure modes
  • Testing transformations (input → output pairs)
  • Comparing multiple implementations or configurations

When NOT to Use Table-Driven Tests

  • Single test scenario (use simple test function)
  • Complex setup/teardown per case (use separate test functions)
  • Tests requiring different assertions per case (separate functions clearer)

Template Structure

The template below is copy-pasteable actual Go code (not pseudocode). Replace placeholders with your specific types and function names.

Key components explained:

  1. Test struct with standard fields: - name: Test case description for t.Run() (required) - input: Input data for the function under test - want: Expected output value - wantErr: Whether this test case expects an error

  2. Test table: Slice of test structs with multiple scenarios

  3. for loop with t.Run(): Executes each test case as a subtest - Enables running individual tests: go test -run TestFunction/specific_case - Enables parallel execution: add t.Parallel() if needed - Provides clear output showing which case failed

  4. Error handling pattern: - Check wantErr first with early return - Prevents false positives from missing error checks

  5. Success assertions: - assert.NoError first to catch unexpected errors - assert.Equal to verify output matches expected

Complete Copy-Pasteable Template

Replace the following placeholders:

  • TestFunction: Your test function name (e.g., TestValidateID)
  • FunctionUnderTest: The function being tested (e.g., ValidateID)
  • InputType: Type of input parameter (e.g., string, *Movie)
  • OutputType: Type of return value (e.g., bool, string)

```go

func TestFunction(t *testing.T) {
    tests := []struct {
        name    string      // Test case name for t.Run()
        input   InputType   // Input data
        want    OutputType  // Expected output
        wantErr bool        // Expect error?
    }{
        {
            name:    "success case",
            input:   validInput,
            want:    expectedOutput,
            wantErr: false,
        },
        {
            name:    "error case",
            input:   invalidInput,
            want:    zeroValue,  // Use zero value when expecting error
            wantErr: true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := FunctionUnderTest(tt.input)

            if tt.wantErr {
                assert.Error(t, err)
                return  // Early return prevents checking 'got' on error path
            }

            assert.NoError(t, err)
            assert.Equal(t, tt.want, got)
        })
    }
}

```

Data Pattern Decision Tree

Choose your test data pattern based on complexity:

  1. Inline Data (Simple): - Use when: Input/output are primitives or small structs (≤5 fields) - Example: Strings, numbers, booleans, simple validation - Benefits: Readable, self-contained, easy to understand

  2. Builder Pattern (Complex Entities): - Use when: Input/output are domain models (Movie, Actress) - Use: testutil.NewMovieBuilder().WithTitle("X").Build() - Benefits: Fluent API, sensible defaults, less boilerplate - See: internal/testutil/builders.go

  3. Golden Files (Large Text): - Use when: Output is large text/HTML/JSON/XML - Use: testutil.LoadGoldenFile(t, "file.golden") - Benefits: Keeps tests clean, snapshot testing for complex output - See: internal/testutil/helpers.go

Examples

See internal/testutil/template_test.go for working examples:

  • Example 1: Simple validation with inline data
  • Example 2: Entity transformation with builders
  • Example 3: Large output with golden files

Canonical reference: internal/matcher/matcher_test.go

  • 76 test cases demonstrating table-driven pattern
  • Shows naming conventions and structure
  • Production-quality example

Common Mistakes to Avoid

  • ❌ Missing name field (breaks t.Run naming)
  • ❌ Not using t.Run() (can't run individual subtests)
  • ❌ Hardcoding file paths (use filepath.Join or golden file helpers)
  • ❌ Testing too many behaviors in one table (split into separate test functions)
  • ❌ Using bare if statements instead of testify assertions
  • ❌ Not checking error when wantErr is true (allows silent failures)

Naming Conventions

  • Test function: TestFunctionName (capitalized, no underscores)
  • Subtest names: tt.name field (descriptive, spaces allowed, lowercase preferred)
  • Test file: package_test.go (matches source file)
  • Example: TestParseMovieID with subtests "valid format", "invalid format"

Index

Examples

Constants

View Source
const TableDrivenTestTemplate = `` /* 604-byte string literal not displayed */

TableDrivenTestTemplate is a documentation placeholder. This file serves as a reference template and does not export runnable code. Copy the template from the godoc comments above when creating new tests.

For working examples, see:

  • internal/testutil/template_test.go (demonstrates all 3 data patterns)
  • internal/matcher/matcher_test.go (canonical 76-case example)

Template location for AI agents: internal/testutil/template.go

Variables

This section is empty.

Functions

func AssertFileExists

func AssertFileExists(t *testing.T, path string)

AssertFileExists checks that a file exists at the given path. Fails the test if the file does not exist.

Example:

testutil.AssertFileExists(t, "/path/to/file.txt")

func AssertFileNotExists

func AssertFileNotExists(t *testing.T, path string)

AssertFileNotExists checks that a file does NOT exist at the given path. Fails the test if the file exists.

Example:

testutil.AssertFileNotExists(t, "/path/to/deleted.txt")

func CaptureOutput

func CaptureOutput(t *testing.T, fn func()) (stdout, stderr string)

CaptureOutput captures stdout and stderr from a function execution. This is useful for testing CLI commands that write to console.

Example:

stdout, stderr := testutil.CaptureOutput(t, func() {
    cmd.Execute()
})

func CompareGoldenFile

func CompareGoldenFile(t *testing.T, name string, actual []byte)

CompareGoldenFile compares actual test output against a golden file. If the content doesn't match, the test fails with a readable diff showing the differences. Golden files must be in testdata/*.golden relative to the test file.

This is useful for snapshot testing complex outputs like NFO XML, JSON responses, or templates.

Example:

func TestAPIResponse(t *testing.T) {
    response := api.GetMovie("IPX-123")
    actual, _ := json.MarshalIndent(response, "", "  ")
    testutil.CompareGoldenFile(t, "movie_response.json.golden", actual)
}
Example

Example demonstrates comparing actual output against a golden file.

// In a real test:
// func TestAPIResponse(t *testing.T) {
//     response := api.GetMovie("IPX-123")
//     actual, _ := json.MarshalIndent(response, "", "  ")
//     testutil.CompareGoldenFile(t, "movie_response.json.golden", actual)
// }

func CreateRootCommandWithConfig

func CreateRootCommandWithConfig(t *testing.T, configPath string) *cobra.Command

CreateRootCommandWithConfig creates a root cobra command with the config flag set. This is the standard pattern for testing commands that need access to --config flag.

Example:

rootCmd := testutil.CreateRootCommandWithConfig(t, configPath)
cmd := mycommand.NewCommand()
rootCmd.AddCommand(cmd)
rootCmd.SetArgs([]string{"mycommand", "arg1"})

func CreateTestConfig

func CreateTestConfig(t *testing.T, customizeFn func(*config.Config)) (configPath string, cfg *config.Config)

CreateTestConfig creates a test configuration file with optional customizations. Returns the config path and the config object.

Example:

configPath, cfg := testutil.CreateTestConfig(t, func(cfg *config.Config) {
    cfg.Scrapers.Priority = []string{"dmm", "r18dev"}
})

func CreateTestVideoFile

func CreateTestVideoFile(t *testing.T, dir string, filename string) string

CreateTestVideoFile creates a test video file with dummy content. Returns the full path to the created file.

Example:

videoPath := testutil.CreateTestVideoFile(t, tmpDir, "IPX-535.mp4")

func GoldenFilePath

func GoldenFilePath(t *testing.T, packageName, filename string) string

GoldenFilePath returns the absolute path to a golden file in a package's testdata directory. This function is useful when you need to access golden files from nested package directories.

Parameters:

  • t: The testing.T instance (used for determining the package path)
  • packageName: The package subdirectory (e.g., "dmm", "r18dev")
  • filename: The golden file name (e.g., "response.html.golden")

Returns the path: internal/scraper/{packageName}/testdata/{filename}

Example:

path := testutil.GoldenFilePath(t, "dmm", "search_success.html.golden")
// Returns: "internal/scraper/dmm/testdata/search_success.html.golden"

func LoadGoldenFile

func LoadGoldenFile(t *testing.T, name string) []byte

LoadGoldenFile loads a golden file from the testdata directory relative to the caller's test. Golden files are reference files used for snapshot testing complex outputs like XML or JSON. The file must be located in testdata/*.golden relative to the test file.

Returns the file contents as []byte. Fails the test if the file cannot be read.

Example:

func TestNFOGeneration(t *testing.T) {
    expected := testutil.LoadGoldenFile(t, "movie_complete.xml.golden")
    actual := nfo.Generate(movie)
    assert.Equal(t, expected, actual)
}
Example

Example demonstrates loading and comparing a golden file for NFO XML testing.

// In a real test:
// func TestNFOGeneration(t *testing.T) {
//     movie := testutil.NewMovieBuilder().Build()
//     actual := nfo.Generate(movie)
//     expected := testutil.LoadGoldenFile(t, "movie_complete.xml.golden")
//     assert.Equal(t, expected, actual)
// }

func SetupTestDB

func SetupTestDB(t *testing.T) (configPath string, dbPath string)

SetupTestDB creates a temporary database with migrations for testing. Returns the config path and database path. The database directory is automatically created.

Example:

configPath, dbPath := testutil.SetupTestDB(t)
// Use configPath to load config
// Database is ready with all migrations applied

func SetupTestDBWithConfig

func SetupTestDBWithConfig(t *testing.T, customizeFn func(*config.Config)) (configPath string, dbPath string)

SetupTestDBWithConfig creates a temporary database with custom config options. This allows tests to customize the config before database creation.

Example:

configPath, dbPath := testutil.SetupTestDBWithConfig(t, func(cfg *config.Config) {
    cfg.Scrapers.Priority = []string{"r18dev"}
})

func UpdateGoldenFile

func UpdateGoldenFile(name string, content []byte) error

UpdateGoldenFile writes content to a golden file in the testdata directory. This function is intended for MANUAL USE ONLY during test development to create or update golden files. It should NOT be called in automated tests.

Creates the testdata/ directory if it doesn't exist. Returns an error if the file cannot be written.

Example usage during test development:

// Temporarily add this to generate/update the golden file
func TestGenerateGolden(t *testing.T) {
    output := generateComplexOutput()
    err := testutil.UpdateGoldenFile("output.golden", output)
    require.NoError(t, err)
}
// Remove after golden file is created
Example

Example demonstrates manual golden file creation workflow.

// During test development ONLY (not in automated tests):
// func TestGenerateGolden(t *testing.T) {
//     output := generateComplexOutput()
//     err := testutil.UpdateGoldenFile("output.golden", output)
//     require.NoError(t, err)
//     // Remove this test after golden file is created
// }

Types

type ActressBuilder

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

ActressBuilder constructs Actress test entities using the builder pattern with fluent API. It provides sensible defaults and method chaining for easy test data creation.

Default values:

  • FirstName: "Test Actress"
  • DMMID: 0 (use WithDMMID to set canonical test value "123456")

Example usage:

actress := testutil.NewActressBuilder().
    WithName("Jane Doe").
    WithDMMID("123456").
    Build()
Example

Example test demonstrating actress builder usage patterns.

// Create an actress with default values
defaultActress := NewActressBuilder().Build()
_ = defaultActress // defaultActress.FirstName == "Test Actress"

// Create a custom actress with canonical test DMMID
customActress := NewActressBuilder().
	WithName("Jane Doe").
	WithDMMID("123456").
	Build()
_ = customActress

// Output shows the builder pattern in action

func NewActressBuilder

func NewActressBuilder() *ActressBuilder

NewActressBuilder creates a new ActressBuilder with sensible defaults. Default: FirstName="Test Actress"

func (*ActressBuilder) Build

func (b *ActressBuilder) Build() *models.Actress

Build returns the constructed Actress instance.

func (*ActressBuilder) WithBirthdate

func (b *ActressBuilder) WithBirthdate(date time.Time) *ActressBuilder

WithBirthdate sets the birthdate and returns the builder for chaining.

func (*ActressBuilder) WithDMMID

func (b *ActressBuilder) WithDMMID(id string) *ActressBuilder

WithDMMID sets the DMM ID (used for deduplication) and returns the builder for chaining. Canonical test value: "123456"

func (*ActressBuilder) WithName

func (b *ActressBuilder) WithName(name string) *ActressBuilder

WithName sets the actress first name and returns the builder for chaining.

type MovieBuilder

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

MovieBuilder constructs Movie test entities using the builder pattern with fluent API. It provides sensible defaults and method chaining for easy test data creation.

Default values:

  • ID: "IPX-123" (canonical test movie ID)
  • ContentID: "ipx00123"
  • Title: "Test Movie"

Example usage:

movie := testutil.NewMovieBuilder().
    WithTitle("Custom Title").
    WithActresses([]string{"Actress 1", "Actress 2"}).
    WithGenres([]string{"Drama"}).
    Build()
Example

Example test demonstrating usage patterns (documentation via example).

// Create a movie with all default values
defaultMovie := NewMovieBuilder().Build()
_ = defaultMovie // defaultMovie.ID == "IPX-123", defaultMovie.Title == "Test Movie"

// Create a custom movie with method chaining
customMovie := NewMovieBuilder().
	WithTitle("My Custom Movie").
	WithActresses([]string{"Actress 1", "Actress 2"}).
	WithGenres([]string{"Drama", "Romance"}).
	Build()
_ = customMovie

// Output shows the builder pattern in action

func NewMovieBuilder

func NewMovieBuilder() *MovieBuilder

NewMovieBuilder creates a new MovieBuilder with sensible defaults. Default: ID="IPX-123", ContentID="ipx00123", Title="Test Movie"

func (*MovieBuilder) Build

func (b *MovieBuilder) Build() *models.Movie

Build returns the constructed Movie instance.

func (*MovieBuilder) WithActresses

func (b *MovieBuilder) WithActresses(actresses []string) *MovieBuilder

WithActresses sets the actresses and returns the builder for chaining. The actresses parameter is converted to the models.Actress format.

func (*MovieBuilder) WithContentID

func (b *MovieBuilder) WithContentID(contentID string) *MovieBuilder

WithContentID sets the content ID and returns the builder for chaining.

func (*MovieBuilder) WithCoverURL

func (b *MovieBuilder) WithCoverURL(url string) *MovieBuilder

WithCoverURL sets the cover URL and returns the builder for chaining.

func (*MovieBuilder) WithDescription

func (b *MovieBuilder) WithDescription(description string) *MovieBuilder

WithDescription sets the description and returns the builder for chaining.

func (*MovieBuilder) WithGenres

func (b *MovieBuilder) WithGenres(genres []string) *MovieBuilder

WithGenres sets the genres and returns the builder for chaining. The genres parameter is converted to the models.Genre format.

func (*MovieBuilder) WithID

func (b *MovieBuilder) WithID(id string) *MovieBuilder

WithID sets the movie ID and returns the builder for chaining.

func (*MovieBuilder) WithReleaseDate

func (b *MovieBuilder) WithReleaseDate(date time.Time) *MovieBuilder

WithReleaseDate sets the release date and returns the builder for chaining.

func (*MovieBuilder) WithStudio

func (b *MovieBuilder) WithStudio(studio string) *MovieBuilder

WithStudio sets the maker (studio) and returns the builder for chaining.

func (*MovieBuilder) WithTitle

func (b *MovieBuilder) WithTitle(title string) *MovieBuilder

WithTitle sets the movie title and returns the builder for chaining.

type ScraperResultBuilder

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

ScraperResultBuilder constructs ScraperResult test entities using the builder pattern with fluent API. It provides sensible defaults and method chaining for easy test data creation.

Default values:

  • Source: "dmm" (canonical test scraper)
  • ContentID: "ABC-123" (canonical test content ID)
  • Title: "Test Movie"
  • Language: "ja"

Required fields validation on Build():

  • Source (panics if empty)
  • ContentID (panics if empty or invalid format)
  • Title (panics if empty)

Example usage:

result := testutil.NewScraperResultBuilder().
    WithSource("dmm").
    WithContentID("IPX-123").
    WithTitle("Test Movie").
    WithActresses("Actress A", "Actress B").
    WithGenres("Drama", "Romance").
    Build()
Example

ExampleScraperResultBuilder demonstrates the builder pattern for creating test ScraperResult instances.

// Create a scraper result with default values
defaultResult := NewScraperResultBuilder().Build()
_ = defaultResult // defaultResult.Source == "dmm", defaultResult.ContentID == "ABC-123"

// Create a custom scraper result with specific values
releaseDate := time.Date(2025, 3, 15, 0, 0, 0, 0, time.UTC)
customResult := NewScraperResultBuilder().
	WithSource("dmm").
	WithContentID("IPX-123").
	WithTitle("Custom Movie Title").
	WithActresses("Actress A", "Actress B").
	WithGenres("Drama", "Romance").
	WithReleaseDate(releaseDate).
	WithCoverURL("https://pics.dmm.co.jp/digital/video/ipx123/ipx123ps.jpg").
	WithMaker("Test Studio").
	WithRuntime(120).
	Build()
_ = customResult

// Output shows the builder pattern in action

func NewScraperResultBuilder

func NewScraperResultBuilder() *ScraperResultBuilder

NewScraperResultBuilder creates a new ScraperResultBuilder with sensible defaults. Default: Source="dmm", ContentID="ABC-123", Title="Test Movie", Language="ja"

func (*ScraperResultBuilder) Build

Build returns the constructed ScraperResult instance. Panics if required fields are missing or invalid:

  • Source must not be empty
  • ContentID must not be empty and must match regex ^[A-Z]{2,5}-\d{3,5}$
  • Title must not be empty

func (*ScraperResultBuilder) WithActresses

func (b *ScraperResultBuilder) WithActresses(actresses ...string) *ScraperResultBuilder

WithActresses sets the actresses and returns the builder for chaining. Variadic parameter for convenient test data creation.

func (*ScraperResultBuilder) WithContentID

func (b *ScraperResultBuilder) WithContentID(contentID string) *ScraperResultBuilder

WithContentID sets the content ID and returns the builder for chaining. Required field. Must match regex: ^[A-Z]{2,5}-\d{3,5}$ (e.g., "ABC-123", "IPXYZ-12345")

func (*ScraperResultBuilder) WithCoverURL

func (b *ScraperResultBuilder) WithCoverURL(url string) *ScraperResultBuilder

WithCoverURL sets the cover URL and returns the builder for chaining.

func (*ScraperResultBuilder) WithDescription

func (b *ScraperResultBuilder) WithDescription(description string) *ScraperResultBuilder

WithDescription sets the description and returns the builder for chaining.

func (*ScraperResultBuilder) WithGenres

func (b *ScraperResultBuilder) WithGenres(genres ...string) *ScraperResultBuilder

WithGenres sets the genres and returns the builder for chaining. Variadic parameter for convenient test data creation.

func (*ScraperResultBuilder) WithLabel

func (b *ScraperResultBuilder) WithLabel(label string) *ScraperResultBuilder

WithLabel sets the label and returns the builder for chaining.

func (*ScraperResultBuilder) WithMaker

func (b *ScraperResultBuilder) WithMaker(maker string) *ScraperResultBuilder

WithMaker sets the maker (studio) and returns the builder for chaining.

func (*ScraperResultBuilder) WithPosterURL

func (b *ScraperResultBuilder) WithPosterURL(url string) *ScraperResultBuilder

WithPosterURL sets the poster URL and returns the builder for chaining.

func (*ScraperResultBuilder) WithReleaseDate

func (b *ScraperResultBuilder) WithReleaseDate(date time.Time) *ScraperResultBuilder

WithReleaseDate sets the release date and returns the builder for chaining.

func (*ScraperResultBuilder) WithRuntime

func (b *ScraperResultBuilder) WithRuntime(runtime int) *ScraperResultBuilder

WithRuntime sets the runtime in minutes and returns the builder for chaining.

func (*ScraperResultBuilder) WithSeries

func (b *ScraperResultBuilder) WithSeries(series string) *ScraperResultBuilder

WithSeries sets the series and returns the builder for chaining.

func (*ScraperResultBuilder) WithSource

func (b *ScraperResultBuilder) WithSource(source string) *ScraperResultBuilder

WithSource sets the scraper source and returns the builder for chaining. Required field. Common values: "dmm", "r18dev"

func (*ScraperResultBuilder) WithSourceURL

func (b *ScraperResultBuilder) WithSourceURL(url string) *ScraperResultBuilder

WithSourceURL sets the source URL and returns the builder for chaining.

func (*ScraperResultBuilder) WithTitle

func (b *ScraperResultBuilder) WithTitle(title string) *ScraperResultBuilder

WithTitle sets the movie title and returns the builder for chaining. Required field.

Jump to

Keyboard shortcuts

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