Factories

Bob のファクトリー機能は、テスト用のモックデータを簡単に作成するためのツールです。

基本的なファクトリー

データベーステーブルから自動的にファクトリーが生成されます。

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    age INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

生成されるファクトリー:

// UserFactory はテスト用のユーザーを作成するファクトリー
type UserFactory struct {
    Name      string
    Email     string
    Age       *int
    CreatedAt *time.Time
}

// NewUserFactory は新しいファクトリーを作成
func NewUserFactory() *UserFactory {
    return &UserFactory{
        Name:      "Test User",
        Email:     "test@example.com",
        Age:       nil,
        CreatedAt: nil,
    }
}

// Build はUserSetterを構築
func (f *UserFactory) Build() UserSetter {
    return UserSetter{
        Name:  omit.From(f.Name),
        Email: omit.From(f.Email),
        Age:   omit.FromPtr(f.Age),
        CreatedAt: omit.FromPtr(f.CreatedAt),
    }
}

// Create はデータベースにユーザーを作成
func (f *UserFactory) Create(ctx context.Context, db bob.Executor) (*User, error) {
    setter := f.Build()
    return Users.Insert(ctx, db, setter)
}

ファクトリーの使用方法

基本的な使用

func TestUserCreation(t *testing.T) {
    // デフォルト値でユーザーを作成
    user, err := NewUserFactory().Create(ctx, db)
    assert.NoError(t, err)
    assert.Equal(t, "Test User", user.Name)
    
    // カスタム値でユーザーを作成
    user, err = NewUserFactory().
        WithName("John Doe").
        WithEmail("john@example.com").
        WithAge(30).
        Create(ctx, db)
    assert.NoError(t, err)
    assert.Equal(t, "John Doe", user.Name)
}

メソッドチェーン

// WithXXX メソッドが自動生成される
func (f *UserFactory) WithName(name string) *UserFactory {
    f.Name = name
    return f
}

func (f *UserFactory) WithEmail(email string) *UserFactory {
    f.Email = email
    return f
}

func (f *UserFactory) WithAge(age int) *UserFactory {
    f.Age = &age
    return f
}

func (f *UserFactory) WithCreatedAt(createdAt time.Time) *UserFactory {
    f.CreatedAt = &createdAt
    return f
}

複数のオブジェクト作成

func TestMultipleUsers(t *testing.T) {
    // 複数のユーザーを作成
    users := make([]*User, 5)
    for i := 0; i < 5; i++ {
        user, err := NewUserFactory().
            WithName(fmt.Sprintf("User %d", i+1)).
            WithEmail(fmt.Sprintf("user%d@example.com", i+1)).
            Create(ctx, db)
        assert.NoError(t, err)
        users[i] = user
    }
    
    assert.Len(t, users, 5)
}

リレーションシップ付きファクトリー

CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    title VARCHAR(200) NOT NULL,
    content TEXT,
    published BOOLEAN DEFAULT FALSE
);

生成されるファクトリー:

type PostFactory struct {
    UserID    int
    Title     string
    Content   *string
    Published *bool
}

func NewPostFactory() *PostFactory {
    return &PostFactory{
        UserID:    1, // デフォルト値
        Title:     "Test Post",
        Content:   nil,
        Published: nil,
    }
}

// リレーションシップを設定するメソッド
func (f *PostFactory) ForUser(user *User) *PostFactory {
    f.UserID = user.ID
    return f
}

func (f *PostFactory) WithTitle(title string) *PostFactory {
    f.Title = title
    return f
}

func (f *PostFactory) WithContent(content string) *PostFactory {
    f.Content = &content
    return f
}

func (f *PostFactory) Published() *PostFactory {
    published := true
    f.Published = &published
    return f
}

リレーションシップの使用

func TestPostWithUser(t *testing.T) {
    // まずユーザーを作成
    user, err := NewUserFactory().
        WithName("John Doe").
        Create(ctx, db)
    assert.NoError(t, err)
    
    // そのユーザーに関連する投稿を作成
    post, err := NewPostFactory().
        ForUser(user).
        WithTitle("My First Post").
        WithContent("This is my first post").
        Published().
        Create(ctx, db)
    assert.NoError(t, err)
    
    assert.Equal(t, user.ID, post.UserID)
    assert.Equal(t, "My First Post", post.Title)
    assert.True(t, *post.Published)
}

カスタムファクトリーメソッド

// カスタムファクトリーメソッドの例
func NewUserFactoryWithRandomEmail() *UserFactory {
    return NewUserFactory().
        WithEmail(fmt.Sprintf("user%d@example.com", rand.Intn(10000)))
}

func NewPublishedPostFactory() *PostFactory {
    return NewPostFactory().
        WithTitle("Published Post").
        WithContent("This is a published post").
        Published()
}

func NewUserWithPosts(postCount int) (*User, []*Post, error) {
    user, err := NewUserFactory().Create(ctx, db)
    if err != nil {
        return nil, nil, err
    }
    
    posts := make([]*Post, postCount)
    for i := 0; i < postCount; i++ {
        post, err := NewPostFactory().
            ForUser(user).
            WithTitle(fmt.Sprintf("Post %d", i+1)).
            Create(ctx, db)
        if err != nil {
            return nil, nil, err
        }
        posts[i] = post
    }
    
    return user, posts, nil
}

設定オプション

factories:
  enabled: true
  package: "factories"
  output: "./testdata/factories"
  defaults:
    string_length: 10
    integer_range: [1, 100]
    boolean_default: false
  templates:
    - name: "user_with_posts"
      factory: "user"
      relations:
        - name: "posts"
          count: 3

テストでの使用例

func TestUserService(t *testing.T) {
    // テストデータのセットアップ
    user, err := NewUserFactory().
        WithName("Test User").
        WithEmail("test@example.com").
        WithAge(25).
        Create(ctx, db)
    require.NoError(t, err)
    
    // 投稿を作成
    _, err = NewPostFactory().
        ForUser(user).
        WithTitle("Test Post").
        Published().
        Create(ctx, db)
    require.NoError(t, err)
    
    // サービスをテスト
    service := NewUserService(db)
    publishedPosts, err := service.GetPublishedPosts(user.ID)
    require.NoError(t, err)
    assert.Len(t, publishedPosts, 1)
    assert.Equal(t, "Test Post", publishedPosts[0].Title)
}

ランダムデータ生成

func NewRandomUserFactory() *UserFactory {
    return NewUserFactory().
        WithName(RandomName()).
        WithEmail(RandomEmail()).
        WithAge(RandomAge())
}

func RandomName() string {
    names := []string{"John", "Jane", "Bob", "Alice", "Charlie"}
    return names[rand.Intn(len(names))]
}

func RandomEmail() string {
    domains := []string{"example.com", "test.org", "demo.net"}
    return fmt.Sprintf("user%d@%s", rand.Intn(1000), domains[rand.Intn(len(domains))])
}

func RandomAge() int {
    return rand.Intn(50) + 18 // 18-67歳
}

Bob のファクトリー機能により、テストデータの作成が大幅に簡素化され、一貫性のあるテストが書けるようになります。