|

Database Testing for SDETs: SQL Validation, Data Integrity, and Testcontainers

Your API test says 201 Created. But did the data actually land in the database correctly? Did the foreign keys resolve? Did the trigger fire? Database testing validates what API tests assume.

🎭 Want to master this with real projects? Join the Playwright Automation Mastery course at The Testing Academy.

Contents

Why SDETs Need Database Testing

  • API returns 200 but writes wrong data to DB
  • Race conditions cause duplicate records
  • Cascading deletes break referential integrity
  • Migrations fail silently on production data shapes
  • Stored procedures have untested edge cases

Database Test Patterns

import { Client } from 'pg';

const db = new Client({ connectionString: process.env.DATABASE_URL });
await db.connect();

test('API creates user with correct DB state', async ({ request }) => {
  // Act: Create user via API
  const response = await request.post('/api/users', {
    data: { name: 'Jane', email: 'jane@test.com', role: 'admin' }
  });
  const user = await response.json();

  // Assert: Verify database state directly
  const result = await db.query('SELECT * FROM users WHERE id = $1', [user.id]);
  expect(result.rows[0].name).toBe('Jane');
  expect(result.rows[0].email).toBe('jane@test.com');
  expect(result.rows[0].role).toBe('admin');
  expect(result.rows[0].created_at).toBeDefined();

  // Cleanup
  await db.query('DELETE FROM users WHERE id = $1', [user.id]);
});

🚀 Level Up Your Playwright

From locators to CI pipelines — build a production-grade Playwright + TypeScript framework step by step.

Testing Data Integrity

test('cascading delete removes child records', async ({ request }) => {
  // Setup: Create parent with children
  const parent = await createUser(request, { name: 'Parent' });
  await createOrder(request, { userId: parent.id, total: 100 });
  await createOrder(request, { userId: parent.id, total: 200 });

  // Verify children exist
  const before = await db.query('SELECT count(*) FROM orders WHERE user_id = $1', [parent.id]);
  expect(parseInt(before.rows[0].count)).toBe(2);

  // Delete parent
  await request.delete('/api/users/' + parent.id);

  // Verify cascade
  const after = await db.query('SELECT count(*) FROM orders WHERE user_id = $1', [parent.id]);
  expect(parseInt(after.rows[0].count)).toBe(0);
});

test('no orphan records after deletion', async () => {
  const result = await db.query(`
    SELECT o.id FROM orders o
    LEFT JOIN users u ON o.user_id = u.id
    WHERE u.id IS NULL
  `);
  expect(result.rows.length).toBe(0);
});

Testcontainers for Isolated DB Testing

import { PostgreSqlContainer } from '@testcontainers/postgresql';

let container;

test.beforeAll(async () => {
  container = await new PostgreSqlContainer()
    .withDatabase('testdb')
    .start();
  // Run migrations
  await runMigrations(container.getConnectionUri());
});

test.afterAll(async () => {
  await container.stop();
});

SQL Test Checklist

CheckSQL Pattern
No orphansLEFT JOIN … WHERE parent.id IS NULL
No duplicatesGROUP BY … HAVING count(*) > 1
FK integrityForeign key constraint violations
Null checksWHERE required_column IS NULL
Data rangesWHERE price < 0 OR quantity < 0
TimestampsWHERE created_at > updated_at

🎓 Master Playwright End to End

Join hundreds of SDETs building real automation frameworks. Lifetime access, hands-on projects, and a job-ready portfolio.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.