# Test Data Builder

## Problem

Tests that create complex domain objects are verbose and brittle. Each test repeats constructor calls with many parameters, most of which are irrelevant to the scenario. When the constructor signature changes, every test breaks. Shared fixture factories lose clarity about which fields matter for each test.

## Solution

Create a **builder** class with sensible defaults for every field. Each field has a fluent setter (`with_*`) that returns the builder. A `build()` method produces the final object. Tests only specify the fields relevant to the scenario — everything else uses defaults.

## When to Use

- Tests that construct objects with many fields (3+ constructor parameters).
- Multiple tests need similar but slightly different object configurations.
- Domain objects are immutable or use complex construction.

## When to Avoid

- Objects are trivial (1-2 fields) — a direct constructor call is clearer.
- A single factory method with keyword arguments suffices.
- The builder would have more code than the tests it simplifies.

## Pseudocode

```
class UserBuilder:
    name = "default_user"
    email = "default@test.com"
    age = 25
    role = "member"

    function with_name(n):  name = n; return this
    function with_email(e): email = e; return this
    function with_age(a):   age = a; return this
    function with_role(r):  role = r; return this
    function build(): return User(name, email, age, role)

// Test — only specify what matters
test "admin can delete posts":
    admin = UserBuilder().with_role("admin").build()
    assert admin.can_delete_posts()
```

## Python

```python
"""Test Data Builder pattern in Python."""
from dataclasses import dataclass
from typing import Self


# ── Domain model ──────────────────────────────────────────────

@dataclass(frozen=True)
class User:
    name: str
    email: str
    age: int
    role: str

    def can_delete_posts(self) -> bool:
        return self.role == "admin"

    def is_adult(self) -> bool:
        return self.age >= 18

    def display(self) -> str:
        return f"{self.name} <{self.email}> ({self.role}, age {self.age})"


# ── Builder ───────────────────────────────────────────────────

class UserBuilder:
    def __init__(self):
        self._name = "default_user"
        self._email = "default@test.com"
        self._age = 25
        self._role = "member"

    def with_name(self, name: str) -> Self:
        self._name = name
        return self

    def with_email(self, email: str) -> Self:
        self._email = email
        return self

    def with_age(self, age: int) -> Self:
        self._age = age
        return self

    def with_role(self, role: str) -> Self:
        self._role = role
        return self

    def build(self) -> User:
        return User(
            name=self._name,
            email=self._email,
            age=self._age,
            role=self._role,
        )


# Convenience function
def a_user() -> UserBuilder:
    return UserBuilder()


# ── Tests ─────────────────────────────────────────────────────

def test_admin_can_delete_posts():
    admin = a_user().with_role("admin").build()
    assert admin.can_delete_posts()
    print(f"PASS: admin can delete posts -> {admin.display()}")


def test_member_cannot_delete_posts():
    member = a_user().with_name("bob").build()  # role defaults to "member"
    assert not member.can_delete_posts()
    print(f"PASS: member cannot delete posts -> {member.display()}")


def test_minor_is_not_adult():
    minor = a_user().with_name("young_alice").with_age(16).build()
    assert not minor.is_adult()
    print(f"PASS: age {minor.age} is not adult -> {minor.display()}")


def test_builder_creates_independent_objects():
    builder = a_user().with_name("shared")
    user_a = builder.with_age(30).build()
    user_b = builder.with_age(40).build()

    # Note: builder is mutated, so user_b has age 40; user_a was built with age 30
    assert user_a.age == 30
    print(f"PASS: user_a age={user_a.age}, user_b age={user_b.age}")


if __name__ == "__main__":
    test_admin_can_delete_posts()
    test_member_cannot_delete_posts()
    test_minor_is_not_adult()
    test_builder_creates_independent_objects()
    print("\nAll Test Data Builder tests passed.")
```

## Go

```go
// test_data_builder.go
package main

import "fmt"

// ── Domain model ─────────────────────────────────────────────

type User struct {
	Name  string
	Email string
	Age   int
	Role  string
}

func (u User) CanDeletePosts() bool {
	return u.Role == "admin"
}

func (u User) IsAdult() bool {
	return u.Age >= 18
}

func (u User) Display() string {
	return fmt.Sprintf("%s <%s> (%s, age %d)", u.Name, u.Email, u.Role, u.Age)
}

// ── Builder ──────────────────────────────────────────────────

type UserBuilder struct {
	name  string
	email string
	age   int
	role  string
}

func AUser() *UserBuilder {
	return &UserBuilder{
		name:  "default_user",
		email: "default@test.com",
		age:   25,
		role:  "member",
	}
}

func (b *UserBuilder) WithName(name string) *UserBuilder {
	b.name = name
	return b
}

func (b *UserBuilder) WithEmail(email string) *UserBuilder {
	b.email = email
	return b
}

func (b *UserBuilder) WithAge(age int) *UserBuilder {
	b.age = age
	return b
}

func (b *UserBuilder) WithRole(role string) *UserBuilder {
	b.role = role
	return b
}

func (b *UserBuilder) Build() User {
	return User{
		Name:  b.name,
		Email: b.email,
		Age:   b.age,
		Role:  b.role,
	}
}

// ── Tests ────────────────────────────────────────────────────

func testAdminCanDeletePosts() {
	admin := AUser().WithRole("admin").Build()
	if !admin.CanDeletePosts() {
		panic("admin should be able to delete posts")
	}
	fmt.Printf("PASS: admin can delete posts -> %s\n", admin.Display())
}

func testMemberCannotDeletePosts() {
	member := AUser().WithName("bob").Build()
	if member.CanDeletePosts() {
		panic("member should not be able to delete posts")
	}
	fmt.Printf("PASS: member cannot delete posts -> %s\n", member.Display())
}

func testMinorIsNotAdult() {
	minor := AUser().WithName("young_alice").WithAge(16).Build()
	if minor.IsAdult() {
		panic("16-year-old should not be adult")
	}
	fmt.Printf("PASS: age %d is not adult -> %s\n", minor.Age, minor.Display())
}

func testBuilderFluentChaining() {
	user := AUser().
		WithName("carol").
		WithEmail("carol@test.com").
		WithAge(30).
		WithRole("admin").
		Build()
	if user.Name != "carol" || user.Role != "admin" || user.Age != 30 {
		panic("fluent chaining produced wrong result")
	}
	fmt.Printf("PASS: fluent chaining -> %s\n", user.Display())
}

func main() {
	testAdminCanDeletePosts()
	testMemberCannotDeletePosts()
	testMinorIsNotAdult()
	testBuilderFluentChaining()
	fmt.Println("\nAll Test Data Builder tests passed.")
}
```

## JavaScript

```javascript
// test_data_builder.js — Fluent builder for test fixtures

// ── Domain model ─────────────────────────────────────────────

class User {
  constructor(name, email, age, role) {
    this.name = name;
    this.email = email;
    this.age = age;
    this.role = role;
  }

  canDeletePosts() {
    return this.role === "admin";
  }

  isAdult() {
    return this.age >= 18;
  }

  display() {
    return `${this.name} <${this.email}> (${this.role}, age ${this.age})`;
  }
}

// ── Builder ──────────────────────────────────────────────────

class UserBuilder {
  constructor() {
    this._name = "default_user";
    this._email = "default@test.com";
    this._age = 25;
    this._role = "member";
  }

  withName(name) {
    this._name = name;
    return this;
  }
  withEmail(email) {
    this._email = email;
    return this;
  }
  withAge(age) {
    this._age = age;
    return this;
  }
  withRole(role) {
    this._role = role;
    return this;
  }

  build() {
    return new User(this._name, this._email, this._age, this._role);
  }
}

function aUser() {
  return new UserBuilder();
}

// ── Tests ────────────────────────────────────────────────────

function testAdminCanDeletePosts() {
  const admin = aUser().withRole("admin").build();
  console.assert(admin.canDeletePosts());
  console.log(`PASS: admin can delete posts -> ${admin.display()}`);
}

function testMemberCannotDeletePosts() {
  const member = aUser().withName("bob").build();
  console.assert(!member.canDeletePosts());
  console.log(`PASS: member cannot delete posts -> ${member.display()}`);
}

function testMinorIsNotAdult() {
  const minor = aUser().withName("young_alice").withAge(16).build();
  console.assert(!minor.isAdult());
  console.log(`PASS: age ${minor.age} is not adult -> ${minor.display()}`);
}

function testFluentChaining() {
  const user = aUser()
    .withName("carol")
    .withEmail("carol@test.com")
    .withAge(30)
    .withRole("admin")
    .build();
  console.assert(user.name === "carol" && user.role === "admin");
  console.log(`PASS: fluent chaining -> ${user.display()}`);
}

testAdminCanDeletePosts();
testMemberCannotDeletePosts();
testMinorIsNotAdult();
testFluentChaining();
console.log("\nAll Test Data Builder tests passed.");
```

## Related Patterns

- **Builder** — the test data builder is an application of the builder pattern specialised for constructing test fixtures.
- **Prototype** — clone a base fixture using prototype/copy to create variations without respecifying all defaults.
- **Fluent Interface** — test data builders use a fluent interface to make fixture construction readable and expressive.
