Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    cameronapak

    bknd-testing

    cameronapak/bknd-testing
    Coding
    23
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Use when writing tests for Bknd applications, setting up test infrastructure, creating unit/integration tests, or testing API endpoints.

    SKILL.md

    Testing Bknd Applications

    Write and run tests for Bknd applications using Bun Test or Vitest with in-memory databases for isolation.

    Prerequisites

    • Bknd project set up locally
    • Test runner installed (Bun or Vitest)
    • Understanding of async/await patterns

    When to Use UI Mode

    • Manual integration testing via admin panel
    • Verifying data after test runs
    • Quick smoke testing

    When to Use Code Mode

    • Automated unit tests
    • Integration tests
    • CI/CD pipelines
    • Regression testing

    Test Runner Setup

    Bun (Recommended)

    Bun has a built-in test runner:

    # Run all tests
    bun test
    
    # Run specific file
    bun test tests/posts.test.ts
    
    # Watch mode
    bun test --watch
    

    Vitest

    # Install
    bun add -D vitest
    
    # Configure vitest.config.ts
    import { defineConfig } from "vitest/config";
    
    export default defineConfig({
      test: {
        globals: true,
      },
    });
    
    # Run
    npx vitest
    

    In-Memory Database Setup

    Use in-memory SQLite for fast, isolated tests.

    Test Helper Module

    Create tests/helper.ts:

    import { App, createApp as baseCreateApp } from "bknd";
    import { em, entity, text, number, boolean } from "bknd";
    import Database from "libsql";
    
    // Schema for tests
    export const testSchema = em({
      posts: entity("posts", {
        title: text().required(),
        content: text(),
        published: boolean(),
      }),
      comments: entity("comments", {
        body: text().required(),
        author: text(),
      }),
    }, (fn, s) => {
      fn.relation(s.comments).manyToOne(s.posts);
    });
    
    // Create isolated test app with in-memory DB
    export async function createTestApp(options?: {
      seed?: (app: App) => Promise<void>;
    }) {
      const db = new Database(":memory:");
    
      const app = new App({
        connection: { database: db },
        schema: testSchema,
      });
    
      await app.build();
    
      if (options?.seed) {
        await options.seed(app);
      }
    
      return {
        app,
        cleanup: () => {
          db.close();
        },
      };
    }
    
    // Create test API client
    export async function createTestClient(app: App) {
      const baseUrl = "http://localhost:0"; // Placeholder
    
      return {
        data: app.modules.data,
        auth: app.modules.auth,
      };
    }
    

    Bun-Specific Helper

    For Bun's native SQLite:

    import { bunSqlite } from "bknd/adapter/bun";
    import { Database } from "bun:sqlite";
    
    export function createTestConnection() {
      const db = new Database(":memory:");
      return bunSqlite({ database: db });
    }
    

    Unit Testing Patterns

    Testing Entity Operations

    import { describe, test, expect, beforeEach, afterEach } from "bun:test";
    import { createTestApp } from "./helper";
    
    describe("Posts", () => {
      let app: Awaited<ReturnType<typeof createTestApp>>;
    
      beforeEach(async () => {
        app = await createTestApp();
      });
    
      afterEach(() => {
        app.cleanup();
      });
    
      test("creates a post", async () => {
        const result = await app.app.em
          .mutator("posts")
          .insertOne({ title: "Test Post", content: "Hello" });
    
        expect(result.id).toBeDefined();
        expect(result.title).toBe("Test Post");
      });
    
      test("reads posts", async () => {
        // Seed data
        await app.app.em.mutator("posts").insertOne({ title: "Post 1" });
        await app.app.em.mutator("posts").insertOne({ title: "Post 2" });
    
        const posts = await app.app.em.repo("posts").findMany();
    
        expect(posts).toHaveLength(2);
      });
    
      test("updates a post", async () => {
        const created = await app.app.em
          .mutator("posts")
          .insertOne({ title: "Original" });
    
        const updated = await app.app.em
          .mutator("posts")
          .updateOne(created.id, { title: "Updated" });
    
        expect(updated.title).toBe("Updated");
      });
    
      test("deletes a post", async () => {
        const created = await app.app.em
          .mutator("posts")
          .insertOne({ title: "To Delete" });
    
        await app.app.em.mutator("posts").deleteOne(created.id);
    
        const found = await app.app.em.repo("posts").findOne(created.id);
        expect(found).toBeNull();
      });
    });
    

    Testing Relationships

    describe("Comments", () => {
      let app: Awaited<ReturnType<typeof createTestApp>>;
    
      beforeEach(async () => {
        app = await createTestApp();
      });
    
      afterEach(() => app.cleanup());
    
      test("creates comment with relation", async () => {
        const post = await app.app.em
          .mutator("posts")
          .insertOne({ title: "Parent Post" });
    
        const comment = await app.app.em
          .mutator("comments")
          .insertOne({
            body: "Great post!",
            posts_id: post.id,
          });
    
        expect(comment.posts_id).toBe(post.id);
      });
    
      test("loads comments with post", async () => {
        const post = await app.app.em
          .mutator("posts")
          .insertOne({ title: "Post" });
    
        await app.app.em.mutator("comments").insertOne({
          body: "Comment 1",
          posts_id: post.id,
        });
    
        const comments = await app.app.em.repo("comments").findMany({
          with: { posts: true },
        });
    
        expect(comments[0].posts).toBeDefined();
        expect(comments[0].posts.title).toBe("Post");
      });
    });
    

    Integration Testing

    HTTP API Testing

    Test the full HTTP stack:

    import { describe, test, expect, beforeAll, afterAll } from "bun:test";
    import { serve } from "bknd/adapter/bun";
    
    describe("API Integration", () => {
      let server: ReturnType<typeof Bun.serve>;
      const port = 3999;
      const baseUrl = `http://localhost:${port}`;
    
      beforeAll(async () => {
        server = Bun.serve({
          port,
          fetch: (await serve({
            connection: { url: ":memory:" },
            schema: testSchema,
          })).fetch,
        });
      });
    
      afterAll(() => {
        server.stop();
      });
    
      test("GET /api/data/posts returns 200", async () => {
        const res = await fetch(`${baseUrl}/api/data/posts`);
        expect(res.status).toBe(200);
    
        const data = await res.json();
        expect(data).toEqual({ data: [] });
      });
    
      test("POST /api/data/posts creates record", async () => {
        const res = await fetch(`${baseUrl}/api/data/posts`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ title: "API Test" }),
        });
    
        expect(res.status).toBe(201);
    
        const { data } = await res.json();
        expect(data.title).toBe("API Test");
      });
    });
    

    Testing with SDK Client

    import { Api } from "bknd/client";
    
    describe("SDK Integration", () => {
      let api: Api;
      let server: ReturnType<typeof Bun.serve>;
    
      beforeAll(async () => {
        // Start test server
        server = await startTestServer();
        api = new Api({ host: "http://localhost:3999" });
      });
    
      afterAll(() => server.stop());
    
      test("creates and reads via SDK", async () => {
        const created = await api.data.createOne("posts", {
          title: "SDK Test",
        });
    
        expect(created.ok).toBe(true);
    
        const read = await api.data.readOne("posts", created.data.id);
        expect(read.data.title).toBe("SDK Test");
      });
    });
    

    Testing Authentication

    Auth Flow Testing

    describe("Authentication", () => {
      let app: Awaited<ReturnType<typeof createTestApp>>;
    
      beforeEach(async () => {
        app = await createTestApp({
          auth: {
            enabled: true,
            strategies: {
              password: {
                hashing: "plain", // Only for tests!
              },
            },
          },
        });
      });
    
      afterEach(() => app.cleanup());
    
      test("registers a user", async () => {
        const auth = app.app.modules.auth;
    
        const result = await auth.register({
          email: "test@example.com",
          password: "password123",
        });
    
        expect(result.user).toBeDefined();
        expect(result.user.email).toBe("test@example.com");
      });
    
      test("login with correct password", async () => {
        const auth = app.app.modules.auth;
    
        // Register first
        await auth.register({
          email: "test@example.com",
          password: "password123",
        });
    
        // Then login
        const result = await auth.login({
          email: "test@example.com",
          password: "password123",
        });
    
        expect(result.token).toBeDefined();
      });
    
      test("login with wrong password fails", async () => {
        const auth = app.app.modules.auth;
    
        await auth.register({
          email: "test@example.com",
          password: "correct",
        });
    
        await expect(
          auth.login({
            email: "test@example.com",
            password: "wrong",
          })
        ).rejects.toThrow();
      });
    });
    

    Mocking Patterns

    Mocking Fetch

    import { mock, jest } from "bun:test";
    
    describe("External API calls", () => {
      let originalFetch: typeof fetch;
    
      beforeAll(() => {
        originalFetch = global.fetch;
        // @ts-ignore
        global.fetch = jest.fn(() =>
          Promise.resolve(
            new Response(JSON.stringify({ success: true }), {
              status: 200,
              headers: { "Content-Type": "application/json" },
            })
          )
        );
      });
    
      afterAll(() => {
        global.fetch = originalFetch;
      });
    
      test("FetchTask uses mocked fetch", async () => {
        const task = new FetchTask("test", {
          url: "https://api.example.com/data",
          method: "GET",
        });
    
        const result = await task.run();
        expect(result.success).toBe(true);
        expect(global.fetch).toHaveBeenCalled();
      });
    });
    

    Mocking Drivers

    describe("Email sending", () => {
      test("uses mock email driver", async () => {
        const sentEmails: any[] = [];
    
        const app = await createTestApp({
          drivers: {
            email: {
              send: async (to, subject, body) => {
                sentEmails.push({ to, subject, body });
                return { id: "mock-id" };
              },
            },
          },
        });
    
        // Trigger something that sends email
        await app.app.drivers.email.send(
          "user@example.com",
          "Test",
          "Body"
        );
    
        expect(sentEmails).toHaveLength(1);
        expect(sentEmails[0].to).toBe("user@example.com");
    
        app.cleanup();
      });
    });
    

    Test Data Factories

    Create reusable factories for test data:

    // tests/factories.ts
    let counter = 0;
    
    export function createPostData(overrides = {}) {
      counter++;
      return {
        title: `Test Post ${counter}`,
        content: `Content for post ${counter}`,
        published: false,
        ...overrides,
      };
    }
    
    export function createUserData(overrides = {}) {
      counter++;
      return {
        email: `user${counter}@test.com`,
        password: "password123",
        ...overrides,
      };
    }
    
    // Usage in tests
    test("creates multiple posts", async () => {
      const posts = await Promise.all([
        app.em.mutator("posts").insertOne(createPostData()),
        app.em.mutator("posts").insertOne(createPostData({ published: true })),
        app.em.mutator("posts").insertOne(createPostData()),
      ]);
    
      expect(posts).toHaveLength(3);
    });
    

    Testing Flows

    import { Flow, FetchTask, Condition } from "bknd/flows";
    
    describe("Flows", () => {
      test("executes flow with tasks", async () => {
        const task1 = new FetchTask("fetch", {
          url: "https://example.com/api",
          method: "GET",
        });
    
        const flow = new Flow("testFlow", [task1]);
    
        const execution = await flow.start({ input: "value" });
    
        expect(execution.hasErrors()).toBe(false);
        expect(execution.getResponse()).toBeDefined();
      });
    
      test("handles task errors", async () => {
        const failingTask = new FetchTask("fail", {
          url: "https://invalid-url-that-fails.test",
          method: "GET",
        });
    
        const flow = new Flow("failFlow", [failingTask]);
        const execution = await flow.start({});
    
        expect(execution.hasErrors()).toBe(true);
        expect(execution.getErrors()).toHaveLength(1);
      });
    });
    

    CI/CD Configuration

    GitHub Actions

    # .github/workflows/test.yml
    name: Tests
    
    on: [push, pull_request]
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - uses: oven-sh/setup-bun@v1
            with:
              bun-version: latest
    
          - run: bun install
          - run: bun test
    

    Pre-commit Hook

    # .husky/pre-commit
    #!/bin/sh
    bun test --bail
    

    Project Structure

    my-bknd-app/
    ├── src/
    │   └── ...
    ├── tests/
    │   ├── helper.ts          # Test utilities
    │   ├── factories.ts       # Data factories
    │   ├── unit/
    │   │   ├── posts.test.ts
    │   │   └── auth.test.ts
    │   └── integration/
    │       ├── api.test.ts
    │       └── flows.test.ts
    ├── bknd.config.ts
    └── package.json
    

    Common Pitfalls

    Database Not Isolated

    Problem: Tests share state, causing flaky tests.

    Solution: Create fresh in-memory DB per test:

    beforeEach(async () => {
      app = await createTestApp();  // New DB each time
    });
    
    afterEach(() => {
      app.cleanup();  // Close connection
    });
    

    Async Cleanup Issues

    Problem: Tests hang or leak resources.

    Solution: Always await cleanup:

    afterEach(async () => {
      await app.cleanup();
    });
    
    afterAll(async () => {
      await server.stop();
    });
    

    Missing await on Assertions

    Problem: Test passes before async operation completes.

    Solution: Always await async operations:

    // WRONG
    test("fails silently", () => {
      expect(api.data.readMany("posts")).resolves.toBeDefined();
    });
    
    // CORRECT
    test("properly awaited", async () => {
      const result = await api.data.readMany("posts");
      expect(result).toBeDefined();
    });
    

    Testing Against Production DB

    Problem: Tests modify real data.

    Solution: Always use :memory: or test-specific file:

    // SAFE
    connection: { url: ":memory:" }
    
    // ALSO SAFE
    connection: { url: "file:test-${Date.now()}.db" }
    
    // DANGEROUS - never in tests
    connection: { url: process.env.DB_URL }
    

    DOs and DON'Ts

    DO:

    • Use in-memory databases for speed and isolation
    • Clean up resources in afterEach/afterAll
    • Create test helpers and factories
    • Test both success and error paths
    • Use meaningful test descriptions
    • Keep tests independent of each other

    DON'T:

    • Share database state between tests
    • Use production credentials in tests
    • Skip await on async operations
    • Write tests that depend on execution order
    • Use plain password hashing outside tests
    • Commit test database files

    Related Skills

    • bknd-local-setup - Development environment setup
    • bknd-debugging - Troubleshooting test failures
    • bknd-seed-data - Creating test data patterns
    • bknd-crud-create - Understanding data operations
    • bknd-setup-auth - Auth configuration for tests
    Repository
    cameronapak/freedom-stack-v3
    Files