Writing Your First Supabase Test in TypeScript

Dan Lynch

Dan Lynch

Nov 17, 2025

lesson header image

Previously: In Setting Up Local Supabase Database Testing, we set up our workspace, module, and testing dependencies. Now let's write our first test.

With your environment configured, you're ready to write your first Supabase test. In this lesson, you'll create test files, understand the getConnections() API, and verify your setup works correctly.

Prerequisites

See Prerequisites. Requires: Complete Setting Up Local Supabase Database Testing.

Write Your First Supabase Test

Navigate back to your new supabase module:

cd packages/my-supabase-app

Create a test file __tests__/basic.test.ts:

import { getConnections, PgTestClient } from 'supabase-test';

let db: PgTestClient;
let teardown: () => Promise<void>;

beforeAll(async () => {
  ({ db, teardown } = await getConnections());
});

afterAll(async () => {
  await teardown();
});

beforeEach(async () => {
  await db.beforeEach();
});

afterEach(async () => {
  await db.afterEach();
});

test('database connection works', async () => {
  const result = await db.query('SELECT 1 as value');
  expect(result.rows[0].value).toBe(1);
});

Understanding the Test Structure

The getConnections() function creates an isolated test database with:

  • db: A PgTestClient connected as the app-level user (typically anon role)
  • teardown(): Cleanup function that closes connections and removes the test database

The beforeEach() and afterEach() hooks provide automatic transaction rollback:

  • beforeEach() starts a transaction and creates a savepoint
  • afterEach() rolls back to the savepoint, ensuring test isolation

Run tests:

pnpm test

Expected Logs

[test-connector] INFO: 🔌 New PgTestClient connected to db-af6511fb-d4fc-4e97-9d30-133db48e8d21
[pg-cache] SUCCESS: pg.Pool db-af6511fb-d4fc-4e97-9d30-133db48e8d21 ended.
[test-connector] INFO: 🧹 Closing all PgTestClients...
[test-connector] SUCCESS: ✅ Closed client for db-af6511fb-d4fc-4e97-9d30-133db48e8d21
[test-connector] SUCCESS: ✅ Closed client for db-af6511fb-d4fc-4e97-9d30-133db48e8d21
[test-connector] SUCCESS: ✅ Closed client for db-af6511fb-d4fc-4e97-9d30-133db48e8d21
[test-connector] INFO: 🧯 Disposing pg pools...
[test-connector] INFO: 🗑️ Dropping seen databases...
[test-connector] WARN: 🧨 Dropped database: db-af6511fb-d4fc-4e97-9d30-133db48e8d21
[test-connector] SUCCESS: ✅ All PgTestClients closed, pools disposed, databases dropped.

 PASS  __tests__/basic.test.ts

  first test

    ✓ should pass (30 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.136 s, estimated 2 s
Ran all test suites.

Using Watch Mode for Rapid Testing

For faster feedback, use watch mode. First, initialize git if you haven't already:

cd /path/to/my-supabase-project
git init .

Then run tests in watch mode:

cd packages/my-supabase-app
pnpm test:watch

Watch mode re-runs tests automatically when you save changes, giving you instant feedback as you develop.

Note: pgpm init defaults to Jest, but pgsql-test and supabase-test are test-framework agnostic—you can use it with Mocha, Vitest, or any other test runner.

Jest watch mode commands:

  • Press p to filter by filename pattern
  • Press t to filter by test name pattern
  • Press a to run all tests
  • Press q to quit watch mode

Create a Real Schema and Test It

Now let's add a schema and table to test. Add a schema change:

pgpm add pets_app

Edit deploy/pets_app.sql:

-- Deploy: pets_app

CREATE SCHEMA IF NOT EXISTS pets;
CREATE TABLE IF NOT EXISTS pets.pets (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name TEXT NOT NULL,
    breed TEXT NOT NULL
);
GRANT USAGE ON SCHEMA pets TO public;
GRANT ALL ON pets.pets TO public;

Now update __tests__/basic.test.ts to test your table:

import { getConnections, PgTestClient } from 'supabase-test';

let db: PgTestClient;
let teardown: () => Promise<void>;

beforeAll(async () => {
  ({ db, teardown } = await getConnections());
});

afterAll(async () => { await teardown(); });
beforeEach(async () => { await db.beforeEach(); });
afterEach(async () => { await db.afterEach(); });

it('should insert a pet into the pets table', async () => {
  const pet = await db.one(
    `INSERT INTO pets.pets (name, breed) 
      VALUES ($1, $2) 
      RETURNING id, name, breed`,
    ['Fido', 'Labrador']
  );

  expect(pet.name).toBe('Fido');
  expect(pet.breed).toBe('Labrador');
});

it('starts with clean state', async () => {
  const result = await db.query('SELECT * FROM pets.pets');
  expect(result.rows).toHaveLength(0);
});

Key Takeaways

  • getConnections() creates an isolated test database with automatic cleanup
  • db.beforeEach() and db.afterEach() provide transaction-based test isolation
  • Watch mode gives instant feedback during development
  • Automatic rollback ensures each test starts with a clean state
  • Test isolation means you can create tables, insert data, and test freely

What's Next

You've written your first Supabase test and verified your setup works. Next, you'll learn how to test Row-Level Security policies and simulate authenticated users with different roles.