Testing

This guide covers how to write and run tests for Nuxt Auto API, including unit tests, integration tests, and E2E tests with both SQLite and Cloudflare D1.

Testing

This guide covers how to write and run tests for Nuxt Auto API, including unit tests, integration tests, and E2E tests with both SQLite and Cloudflare D1.

Table of Contents

Running Tests

# Run all tests
npm run test

# Run tests in watch mode
npm run test:watch

# Run specific test file
npm run test -- test/unit/utils/buildWhereClause.test.ts

# Run D1 tests
npm run test:d1

Test Structure

packages/@websideproject/nuxt-auto-api/
├── test/
│   ├── fixtures/          # Test apps for E2E
│   │   ├── basic/         # Basic CRUD test app
│   │   └── auth/          # With authentication
│   ├── unit/              # Unit tests
│   │   ├── utils/         # Utility function tests
│   │   └── middleware/    # Middleware tests
│   ├── integration/       # Integration tests
│   ├── e2e/               # End-to-end tests
│   └── helpers/           # Test utilities
│       ├── setup.ts       # SQLite setup
│       ├── setup-d1.ts    # D1 setup
│       ├── factories.ts   # Data factories
│       └── mocks.ts       # Mock utilities

Unit Tests

Unit tests focus on testing individual functions and utilities in isolation.

Example: Testing Utility Functions

// test/unit/utils/buildWhereClause.test.ts
import { describe, it, expect } from 'vitest'
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
import { buildWhereClause } from '../../../src/runtime/server/utils/buildWhereClause'

const testTable = sqliteTable('test', {
  id: integer('id').primaryKey(),
  name: text('name'),
  age: integer('age')
})

describe('buildWhereClause', () => {
  it('should build simple equality filter', () => {
    const filter = { name: 'John' }
    const result = buildWhereClause(filter, testTable)

    expect(result).toBeDefined()
  })

  it('should handle $gt operator', () => {
    const filter = { age: { $gt: 18 } }
    const result = buildWhereClause(filter, testTable)

    expect(result).toBeDefined()
  })
})

Example: Testing Middleware

// test/unit/middleware/authz.test.ts
import { describe, it, expect } from 'vitest'
import { createAuthorizationMiddleware } from '../../../src/runtime/server/middleware/authz'
import { createMockContext, createMockUser } from '../../helpers/mocks'

describe('Authorization Middleware', () => {
  it('should allow request with valid permissions', async () => {
    const config = {
      permissions: {
        read: ['user', 'admin']
      }
    }

    const middleware = createAuthorizationMiddleware(config)
    const context = createMockContext({
      user: createMockUser('user'),
      permissions: ['read'],
      operation: 'list'
    })

    await expect(middleware(context)).resolves.not.toThrow()
  })
})

Integration Tests

Integration tests verify that multiple components work together correctly, using a real database.

Setup

// test/integration/crud.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { setupTestDatabase, seedDatabase, cleanDatabase } from '../helpers/setup'
import * as baseSchema from '../../playground/server/database/schema'

describe('CRUD Operations Integration', () => {
  let db: any
  let sqlite: any
  let testData: any

  beforeEach(async () => {
    const setup = await setupTestDatabase(baseSchema)
    db = setup.db
    sqlite = setup.sqlite
    testData = await seedDatabase(db, baseSchema)
  })

  afterEach(async () => {
    await cleanDatabase(db, baseSchema)
    sqlite.close()
  })

  it('should return all posts', async () => {
    const context = createMockContext({
      db,
      schema: baseSchema,
      resource: 'posts',
      operation: 'list',
      query: {}
    })

    const result = await listHandler(context)

    expect(result.data).toBeArray()
    expect(result.data.length).toBeGreaterThan(0)
  })
})

E2E Tests

E2E tests verify the complete flow from HTTP request to database and back.

// test/e2e/full-flow.test.ts
import { describe, it, expect, beforeAll } from 'vitest'
import { setup, $fetch } from '@nuxt/test-utils/e2e'
import { fileURLToPath } from 'node:url'

describe('Full CRUD Flow E2E', async () => {
  await setup({
    rootDir: fileURLToPath(new URL('../fixtures/basic', import.meta.url))
  })

  it('should complete full user journey', async () => {
    // 1. List posts
    const listResponse = await $fetch('/api/posts')
    expect(listResponse.data).toBeArray()

    // 2. Create post
    const createResponse = await $fetch('/api/posts', {
      method: 'POST',
      headers: { 'Authorization': 'Bearer admin-token' },
      body: {
        title: 'E2E Test Post',
        content: 'Test content',
        userId: 1
      }
    })

    expect(createResponse.data).toBeDefined()
    const postId = createResponse.data.id

    // 3. Get created post
    const getResponse = await $fetch(`/api/posts/${postId}`)
    expect(getResponse.data.id).toBe(postId)

    // 4. Update post
    const updateResponse = await $fetch(`/api/posts/${postId}`, {
      method: 'PATCH',
      headers: { 'Authorization': 'Bearer admin-token' },
      body: { title: 'Updated E2E Post' }
    })

    expect(updateResponse.data.title).toBe('Updated E2E Post')

    // 5. Delete post
    const deleteResponse = await $fetch(`/api/posts/${postId}`, {
      method: 'DELETE',
      headers: { 'Authorization': 'Bearer admin-token' }
    })

    expect(deleteResponse.success).toBe(true)
  })
})

Testing with D1

D1 tests use Cloudflare's Miniflare for local testing.

Configuration

// vitest.config.d1.ts
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'

export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        miniflare: {
          d1Databases: {
            DB: 'test-db'
          }
        }
      }
    }
  }
})

D1 Integration Test

// test/integration/crud-d1.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { env } from 'cloudflare:test'
import { setupTestD1Database, seedD1Database } from '../helpers/setup-d1'
import * as baseSchema from '../../playground/server/database/schema'

describe('CRUD Operations with D1', () => {
  let db: any
  let d1: D1Database

  beforeEach(async () => {
    const setup = await setupTestD1Database(env.DB, baseSchema)
    db = setup.db
    d1 = setup.d1

    await seedD1Database(db, baseSchema)
  })

  it('should list posts from D1', async () => {
    const context = createMockContext({
      db,
      schema: baseSchema,
      resource: 'posts',
      operation: 'list'
    })

    const result = await listHandler(context)

    expect(result.data).toBeArray()
    expect(result.data.length).toBeGreaterThan(0)
  })
})

Test Helpers

Database Setup (SQLite)

// test/helpers/setup.ts
import Database from 'better-sqlite3'
import { drizzle } from 'drizzle-orm/better-sqlite3'

export async function setupTestDatabase(schema: any) {
  const sqlite = new Database(':memory:')
  const db = drizzle(sqlite, { schema })
  return { db, sqlite }
}

export async function seedDatabase(db: any, schema: any) {
  const users = await db.insert(schema.users).values([
    { email: 'admin@test.com', name: 'Admin', role: 'admin' },
    { email: 'user@test.com', name: 'User', role: 'user' }
  ]).returning()

  const posts = await db.insert(schema.posts).values([
    { title: 'Post 1', content: 'Content 1', userId: users[0].id }
  ]).returning()

  return { users, posts }
}

export async function cleanDatabase(db: any, schema: any) {
  await db.delete(schema.posts)
  await db.delete(schema.users)
}

Mock Factories

// test/helpers/mocks.ts
export function createMockContext(overrides = {}) {
  return {
    db: null,
    schema: {},
    user: null,
    permissions: [],
    params: {},
    query: {},
    validated: {},
    event: createMockH3Event(),
    resource: 'posts',
    operation: 'list',
    ...overrides
  }
}

export function createMockUser(role = 'user', overrides = {}) {
  return {
    id: 1,
    email: 'test@example.com',
    name: 'Test User',
    role,
    permissions: getRolePermissions(role),
    ...overrides
  }
}

Data Factories

// test/helpers/factories.ts
export const UserFactory = {
  build: (overrides = {}) => ({
    email: `test${Date.now()}@example.com`,
    name: 'Test User',
    role: 'user',
    ...overrides
  }),

  create: async (db, schema, overrides = {}) => {
    const [user] = await db.insert(schema.users)
      .values(UserFactory.build(overrides))
      .returning()
    return user
  }
}

Best Practices

  1. Use in-memory database for unit/integration tests - Fast and isolated
  2. Clean database between tests - Prevents test pollution
  3. Use factories for test data - Reduces coupling and duplication
  4. Test happy path and error cases - Comprehensive coverage
  5. Mock external dependencies - Keep tests fast and deterministic
  6. Use descriptive test names - Clear documentation of behavior
  7. Group related tests - Organize with describe blocks
  8. Keep tests focused - One concept per test

Coverage Goals

  • Unit Tests: 80%+ coverage
  • Integration Tests: All major flows covered
  • E2E Tests: Critical user journeys covered

Run coverage report:

npm run test -- --coverage

Need a Landing Page?

Modern landing pages with optional modules (blog, docs, forms, i18n). Let's discuss your project.

Build Your MVP

Full-stack SaaS development. Expert in database design, multi-tenancy, and scalable architecture.

Deployment Help

Dockerize your backend, set up CI/CD pipelines, deploy to Cloudflare or Hetzner. Early-stage setup.

Suggest a SaaS Tool

Missing a calculator or tool? Suggest what you'd like to see on our site.