Quick Tips
3 min

Test Data Management Strategies

Effective approaches to managing test data for reliable and maintainable tests

...
test-datatestingbest-practicesautomation

Test Data Management Best Practices

1. Data Builders Pattern

Create flexible test data on-the-fly:

class UserBuilder {
  constructor() {
    this.data = {
      email: `user${Date.now()}@test.com`,
      name: 'Test User',
      age: 25,
      role: 'user',
    };
  }
 
  withEmail(email) {
    this.data.email = email;
    return this;
  }
 
  withRole(role) {
    this.data.role = role;
    return this;
  }
 
  build() {
    return this.data;
  }
}
 
// Usage
const admin = new UserBuilder()
  .withEmail('admin@test.com')
  .withRole('admin')
  .build();

2. Fixture Files

Store static test data in JSON files:

// fixtures/users.json
{
  "validUser": {
    "email": "valid@example.com",
    "password": "ValidPass123!"
  },
  "adminUser": {
    "email": "admin@example.com",
    "password": "AdminPass123!"
  }
}
 
// In tests
import users from './fixtures/users.json';
 
test('login with valid user', async () => {
  await login(users.validUser);
  expect(await isLoggedIn()).toBe(true);
});

3. Factory Functions

Generate random but valid data:

import { faker } from '@faker-js/faker';
 
function createUser(overrides = {}) {
  return {
    id: faker.string.uuid(),
    email: faker.internet.email(),
    name: faker.person.fullName(),
    createdAt: faker.date.recent(),
    ...overrides,
  };
}
 
// Usage
const user1 = createUser({ email: 'specific@email.com' });
const user2 = createUser(); // Random data

4. Database Seeding

Prepare consistent database state:

// seeds/testUsers.js
export async function seedUsers(db) {
  await db.users.deleteMany({}); // Clean start
  
  await db.users.createMany([
    { email: 'test1@example.com', role: 'user' },
    { email: 'test2@example.com', role: 'user' },
    { email: 'admin@example.com', role: 'admin' },
  ]);
}
 
// In tests
beforeEach(async () => {
  await seedUsers(db);
});

5. Test Data Cleanup

Always clean up to prevent test interference:

describe('User tests', () => {
  const createdUsers = [];
 
  afterEach(async () => {
    // Clean up all created users
    for (const user of createdUsers) {
      await deleteUser(user.id);
    }
    createdUsers.length = 0;
  });
 
  test('create user', async () => {
    const user = await createUser(userData);
    createdUsers.push(user); // Track for cleanup
    expect(user.id).toBeDefined();
  });
});

Key Principles

  1. Isolation: Each test creates its own data
  2. Idempotency: Tests produce same result every run
  3. Cleanup: Remove test data after use
  4. Realistic: Use data similar to production
  5. Minimal: Only create data you actually need

Anti-Patterns to Avoid

  • ❌ Hardcoding production IDs
  • ❌ Sharing data between tests
  • ❌ Leaving test data in database
  • ❌ Using unrealistic data
  • ❌ Over-complicated data setups

Pro Tips

  • Use faker for realistic random data
  • Keep fixtures small and focused
  • Document data dependencies
  • Use transactions for faster cleanup
  • Consider using test containers for databases

Comments (0)

Loading comments...