supabase-typed-query
Version:
Type-safe query builder and entity pattern for Supabase with TypeScript
350 lines (253 loc) • 11.3 kB
Markdown
# Integration Tests
This document describes how to set up and run integration tests for supabase-typed-query.
## Overview
Integration tests verify that our library works correctly against a real Supabase database. We support two testing approaches:
- **Unit Tests**: Fast, mock-based tests that don't require a database (default for `pnpm test:unit`)
- **Integration Tests**: Real database tests that verify actual Supabase behavior (`pnpm test:integration`)
## Test Architecture
```
supabase-typed-query → Supabase Client → PostgreSQL/Supabase Database
```
## Test Coverage
The integration tests are organized into three main test suites with **65+ comprehensive tests**:
### Query API Tests (`Query.integration.spec.ts`)
- **Basic Query Execution** (6 tests): one(), many(), first(), oneOrThrow(), manyOrThrow(), firstOrThrow()
- **Comparison Operators** (8 tests): gt, gte, lt, lte, neq, in, is null, is true/false
- **Pattern Matching** (2 tests): like, ilike (case-insensitive)
- **OR Chaining** (3 tests): Single OR, multiple OR, OR with IS conditions
- **Functional Operations** (9 tests): map, chained map, filter, filter+map, limit, offset, pagination
- **Soft Delete Operations** (3 tests): includeDeleted(), excludeDeleted(), onlyDeleted()
- **Complex Query Chains** (2 tests): Combined operations, type safety
**Total**: ~33 Query API integration tests
### Entity API Tests (`Entity.integration.spec.ts`)
- **getGlobalItems()** (3 tests): Fetch all, with conditions, soft delete filtering
- **getItem()** (2 tests): Fetch by ID, not found handling
- **addItems()** (2 tests): Single insert, batch insert
- **updateItem()** (2 tests): Update by ID, non-existent item
- **deleteItem()** (2 tests): Hard delete, soft delete
- **deleteItems()** (1 test): Batch delete
- **OrThrow Variants** (6 tests): All \*OrThrow methods with success and error cases
- **Multi-tenancy** (1 test): Partition key support
- **Error Handling** (1 test): Database constraint violations
**Total**: ~20 Entity API integration tests
### Advanced Query Tests (`QueryAdvanced.integration.spec.ts`)
- **Complex Query Chains** (3 tests): OR+filter+map+limit, nested transformations, multiple conditions
- **Pagination Scenarios** (3 tests): Offset-based pagination, limit only, offset only
- **Concurrent Query Execution** (2 tests): Multiple concurrent queries, concurrent map operations
- **Edge Cases** (4 tests): Empty results, long OR chains, no matches, null comparisons
- **Performance Characteristics** (2 tests): Large result sets, multiple filters
- **Type Safety** (2 tests): Types through complex chains, nested transformations
- **Real-world Scenarios** (3 tests): Recent posts query, admin emails, paginated search
**Total**: ~19 Advanced integration tests
### Helper Utilities (`database-setup.ts`)
- Connection management (Supabase/PostgREST)
- Test data creation (single/batch)
- Soft delete test data generation
- Automatic cleanup with test\_ prefix pattern
## Local Development Setup
### Option 1: Using Your Own Supabase Project (Recommended)
1. **Create a Supabase project** (or use existing one):
- Go to [supabase.com](https://supabase.com)
- Create a new project or select existing
- Get your project URL and anon key from Project Settings > API
2. **Set up test schema**:
The integration tests require specific tables in your database. We provide a complete SQL schema file:
**Option A: Using the provided schema file**
```bash
# Copy the SQL schema from test/integration/schema.sql
# and run it in your Supabase SQL editor
cat test/integration/schema.sql
```
**Option B: Run the SQL directly**
```sql
-- Create test tables in your Supabase SQL editor
-- See test/integration/schema.sql for the complete schema
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER,
active BOOLEAN DEFAULT true,
role TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
deleted TIMESTAMPTZ -- Soft delete column
);
-- Posts table
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
content TEXT,
author_id UUID REFERENCES users(id) ON DELETE CASCADE,
status TEXT,
view_count INTEGER DEFAULT 0,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
deleted TIMESTAMPTZ -- Soft delete column
);
-- Comments table
CREATE TABLE comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
text TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
deleted TIMESTAMPTZ -- Soft delete column
);
-- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_active ON users(active) WHERE deleted IS NULL;
CREATE INDEX IF NOT EXISTS idx_posts_author ON posts(author_id);
CREATE INDEX IF NOT EXISTS idx_posts_status ON posts(status) WHERE deleted IS NULL;
CREATE INDEX IF NOT EXISTS idx_comments_post ON comments(post_id);
CREATE INDEX IF NOT EXISTS idx_comments_user ON comments(user_id);
```
> **Note**: The complete schema with indexes and seed data is available in `test/integration/schema.sql`
3. **Configure environment variables**:
```bash
cp .env.integration.example .env.integration
# Edit .env.integration with your Supabase credentials:
TEST_SUPABASE_URL=https://your-project-ref.supabase.co
TEST_SUPABASE_ANON_KEY=your-anon-key-here
```
4. **Run integration tests**:
```bash
pnpm test:integration # Run once
pnpm test:integration:watch # Watch mode
pnpm test:integration:coverage # With coverage
```
### Option 2: Using Supabase CLI (Local)
1. **Install Supabase CLI**:
```bash
npm install -g supabase
```
2. **Start local Supabase**:
```bash
supabase start
```
3. **Configure for local Supabase**:
```bash
# .env.integration
TEST_SUPABASE_URL=http://localhost:54321
TEST_SUPABASE_ANON_KEY=your-local-anon-key # From supabase start output
```
4. **Run tests**:
```bash
pnpm test:integration
```
## Available Test Scripts
```json
{
"test": "Run all unit tests (no database required)",
"test:watch": "Run unit tests in watch mode",
"test:unit": "Run unit tests only",
"test:unit:watch": "Run unit tests in watch mode",
"test:unit:coverage": "Run unit tests with coverage",
"test:integration": "Run integration tests (requires database)",
"test:integration:watch": "Run integration tests in watch mode",
"test:integration:coverage": "Run integration tests with coverage",
"test:ci": "Run both unit and integration tests"
}
```
## Environment Variables
### Integration Tests
- `TEST_SUPABASE_URL` - Your Supabase project URL
- `TEST_SUPABASE_ANON_KEY` - Your Supabase anonymous key
- `TEST_POSTGREST_URL` - Alternative: Direct PostgREST endpoint (for CI)
- `TEST_POSTGREST_ANON_KEY` - Alternative: JWT token for PostgREST
### Legacy Support
These environment variables are also supported for backwards compatibility:
- `SUPABASE_TEST_URL`
- `SUPABASE_TEST_ANON_KEY`
## Writing Integration Tests
Integration tests should be placed in `test/integration/` with the `.integration.spec.ts` suffix:
```typescript
// test/integration/MyFeature.integration.spec.ts
import { beforeAll, describe, expect, it } from "vitest"
import { DatabaseSetup } from "./database-setup"
describe("My Feature Integration Tests", () => {
const dbSetup = new DatabaseSetup()
beforeAll(async () => {
await dbSetup.initialize()
await dbSetup.cleanupTestData()
})
it("should test my feature", async () => {
const client = dbSetup.getClient()
// Create test data
const testData = await dbSetup.createTestData()
// Test your feature using the query builder
const { data, error } = await client.from("users").select("*").eq("id", testData.userId)
expect(error).toBeNull()
expect(data).toBeDefined()
})
})
```
## Best Practices
1. **Use DatabaseSetup**: Always use the `DatabaseSetup` class for consistent database initialization and cleanup
2. **Clean Test Data**: Use test data prefixed with `test_` for easy cleanup:
```typescript
email: "test_user@example.com"
title: "test_my_feature"
```
3. **Deterministic Tests**: Avoid relying on existing database state - create your own test data
4. **Sequential Execution**: Integration tests run sequentially to avoid database conflicts
5. **Error Handling**: Always check for both `error` and `data` in responses:
```typescript
const { data, error } = await client.from("table").select("*")
expect(error).toBeNull()
expect(data).toBeDefined()
```
6. **Cleanup**: The `DatabaseSetup` class automatically cleans up test data before each run
## CI/CD Integration
### GitHub Actions Example
```yaml
name: Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: pnpm install
- name: Run unit tests
run: pnpm test:unit
- name: Run integration tests
env:
TEST_SUPABASE_URL: ${{ secrets.TEST_SUPABASE_URL }}
TEST_SUPABASE_ANON_KEY: ${{ secrets.TEST_SUPABASE_ANON_KEY }}
run: pnpm test:integration
```
Add your Supabase credentials as GitHub secrets:
- `TEST_SUPABASE_URL`
- `TEST_SUPABASE_ANON_KEY`
## Troubleshooting
### "Database connection failed"
- Verify your `TEST_SUPABASE_URL` and `TEST_SUPABASE_ANON_KEY` are correct
- Check that your Supabase project is active and accessible
- Ensure you're using the correct URL format: `https://your-project.supabase.co`
### "Table does not exist" errors
- Integration tests will skip gracefully if tables don't exist
- To run full integration tests, create the test schema (see setup instructions above)
- Alternatively, adjust tests to use your own database schema
### Tests failing intermittently
- Integration tests run sequentially to avoid conflicts
- If tests fail intermittently, check for leftover test data
- Run cleanup manually: Create a test file that calls `dbSetup.cleanupTestData()`
### "No environment variables" warning
- Create `.env.integration` file with your credentials
- Ensure the file is in the project root directory
- Check that file is not ignored by git (it should be in `.gitignore`)
## Performance
- **Unit Tests**: Very fast (~10-50ms per test) - no database required
- **Integration Tests**: Moderate (~100-500ms per test) - requires database connection
Unit tests should always be preferred for fast feedback. Use integration tests to verify actual database behavior and catch issues that mocks might miss.
## Security
- **Never commit** `.env.integration` or `.env.test` files to git
- Use separate Supabase projects for testing vs production
- Consider using Row Level Security (RLS) even for test projects
- Rotate anon keys periodically for test projects