Skip to main content
Learn how to import actual production data to Fixture DB for use in tests.

Loading Fixtures Overview

Real Data

Production data Realistic testing

Auto Relation Resolution

Auto BelongsTo tracking Related data together

Duplicate Prevention

ID-based management Safe import

Simple Command

One-line execution Auto sync

pnpm sonamu fixture import

Import specific records from production DB to Fixture DB.

Basic Usage

pnpm sonamu fixture import [entityId] [recordIds]
Parameters:
  • entityId: Entity name (e.g., “User”, “Post”)
  • recordIds: Array of record IDs to import (comma-separated)

Examples

# Import User ID 1, 2, 3 to Fixture DB
pnpm sonamu fixture import User 1,2,3

# Import Post ID 42 to Fixture DB
pnpm sonamu fixture import Post 42

How It Works

1. Record Extraction

// sonamu/src/testing/fixture-manager.ts
async function importFixture(entityId: string, ids: number[]) {
  const queries = await Promise.all(
    ids.map(async (id) => {
      // Generate import query for each ID
      return await this.getImportQueries(entityId, "id", id);
    })
  );

  // Execute queries
  const wdb = BaseModel.getDB("w");
  for (const query of queries) {
    await wdb.raw(query);
  }
}

2. Auto Relation Tracking

Automatically tracks BelongsTo relations to import all necessary data.
# When importing User #1...
pnpm sonamu fixture import User 1

# Automatically imports:
# - User #1
# - User #1's Profile (OneToOne)
# - Profile's related data
Internal operation:
async function getImportQueries(
  entityId: string,
  field: string,
  id: number
): Promise<string[]> {
  // Prevent circular references
  const recordKey = `${entityId}#${field}#${id}`;
  if (this.visitedRecords.has(recordKey)) {
    return [];
  }
  this.visitedRecords.add(recordKey);

  // Fetch record
  const entity = EntityManager.get(entityId);
  const [row] = await wdb(entity.table).where(field, id).limit(1);

  if (!row) {
    throw new Error(`Cannot find ${entityId}#${id} row.`);
  }

  // Handle BelongsTo relations
  const relatedQueries = [];
  for (const [, relation] of Object.entries(entity.relations)) {
    if (isBelongsToOneRelationProp(relation)) {
      const relatedId = row[`${relation.name}_id`];
      if (relatedId) {
        // Recursively import related data
        relatedQueries.push(
          ...(await this.getImportQueries(relation.with, "id", relatedId))
        );
      }
    }
  }

  // Generate INSERT query
  const selfQuery = `INSERT IGNORE INTO fixture.${entity.table} 
    (SELECT * FROM production.${entity.table} WHERE id = ${id})`;

  return [...relatedQueries, selfQuery];
}

3. Auto Sync

After import, Fixture DB → Test DB sync is automatically executed.
async function fixture_import(entityId: string, recordIds: number[]) {
  await setupFixtureManager();

  // 1. Production → Fixture DB
  await FixtureManager.importFixture(entityId, recordIds);

  // 2. Fixture DB → Test DB
  await FixtureManager.sync();
}

Execution Examples

Import Users

pnpm sonamu fixture import User 1,2,3

{ entityId: 'User', field: 'id', id: 1 }
{ entityId: 'Profile', field: 'id', id: 1 }
INSERT IGNORE INTO fixture.profiles ...
INSERT IGNORE INTO fixture.users ...

{ entityId: 'User', field: 'id', id: 2 }
{ entityId: 'Profile', field: 'id', id: 2 }
INSERT IGNORE INTO fixture.profiles ...
INSERT IGNORE INTO fixture.users ...

 Import complete! Syncing to test DB...

Import Posts

$ pnpm sonamu fixture import Post 42

{ entityId: 'Post', field: 'id', id: 42 }
{ entityId: 'User', field: 'id', id: 5 }
{ entityId: 'Profile', field: 'id', id: 5 }
{ entityId: 'Category', field: 'id', id: 10 }

INSERT IGNORE INTO fixture.profiles ...
INSERT IGNORE INTO fixture.users ...
INSERT IGNORE INTO fixture.categories ...
INSERT IGNORE INTO fixture.posts ...

 Import complete!

Complex Relation Handling

HasMany Relations

# To import User #1 and all their Posts
# 1. Import User
pnpm sonamu fixture import User 1

# 2. Import User's Posts separately
pnpm sonamu fixture import Post 10,11,12

# HasMany is not automatically tracked
# (could import too much data)

ManyToMany Relations

# To import Post #1 and related Tags
# 1. Import Post
pnpm sonamu fixture import Post 1

# 2. Handle junction table data manually
# (or create directly in fixture.ts)

Duplicate Handling

Uses INSERT IGNORE to prevent duplicates.
# Safe to import User #1 twice
pnpm sonamu fixture import User 1
pnpm sonamu fixture import User 1

# Result: User #1 saved only once

Circular Reference Prevention

Tracks visited records to prevent circular references.
// Circular reference example:
// User #1 → Profile #1
// Profile #1 → User #1 (back reference)

// Prevented with visit tracking
private visitedRecords = new Set<string>();

const recordKey = `${entityId}#${field}#${id}`;
if (this.visitedRecords.has(recordKey)) {
  return []; // Already visited - skip
}
this.visitedRecords.add(recordKey);

Best Practices

1. Minimal Data

# ✅ Correct: Only what's needed
pnpm sonamu fixture import User 1,2

# ❌ Wrong: Too much data
pnpm sonamu fixture import User 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

2. Representative Cases

# Select data including various cases
pnpm sonamu fixture import User 1    # Regular user
pnpm sonamu fixture import User 100  # Admin
pnpm sonamu fixture import User 500  # Premium user

3. Use with fixture.ts

// api/src/testing/fixture.ts
import { createFixtureLoader } from "sonamu/test";

export const loadFixtures = createFixtureLoader({
  // Use real data imported from production
  realUser01: async () => {
    const userModel = new UserModel();
    // Assuming ID 1 already exists in fixture DB
    const { user } = await userModel.getUser("C", 1);
    return user;
  },

  // Create new data for testing
  testUser: async () => {
    const userModel = new UserModel();
    const { user } = await userModel.create({
      username: "test",
      email: "test@example.com",
      password: "password",
    });
    return user;
  },
});

Troubleshooting

Record Not Found

Error: Cannot find User#999 row.
Solution:
  • Verify the ID exists in production DB
  • Use correct Entity name

Permission Error

Error: Access denied for user 'readonly'
Solution:
-- Grant write permission to Fixture DB user
GRANT ALL PRIVILEGES ON fixture.* TO 'user'@'%';

Too Much Data

# Slow when importing too many IDs at once
pnpm sonamu fixture import User 1,2,3,...,100

# Solution: Split into multiple executions
pnpm sonamu fixture import User 1,2,3,4,5
pnpm sonamu fixture import User 6,7,8,9,10

Cautions

Cautions when loading fixtures:
  1. Minimal data: Import only minimum necessary records
  2. Check relations: BelongsTo is automatic, HasMany is manual
  3. Duplicate safe: INSERT IGNORE prevents duplicates
  4. Circular references: Handled automatically
  5. Check permissions: Write permission needed for Fixture DB

Next Steps