> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Questions about writing and running tests

## Testing Basics

<AccordionGroup>
  <Accordion title="What testing framework does Sonamu use?">
    Sonamu uses **Vitest**.

    **Features:**

    * Very fast execution based on Vite
    * Jest-compatible API
    * Native TypeScript support
    * HMR support
  </Accordion>

  <Accordion title="How do I run tests?">
    ```bash theme={null}
    # Run all tests
    pnpm test

    # Run specific file only
    pnpm test user.model.test.ts

    # Watch mode (detect file changes)
    pnpm test --watch

    # Coverage report
    pnpm test --coverage

    # UI mode
    pnpm test --ui
    ```
  </Accordion>

  <Accordion title="How do I create test files?">
    **Generate with Scaffolding:**

    1. Access Sonamu UI
    2. Scaffolding tab
    3. Select Entity
    4. Template: Select `model_test`
    5. Click Generate

    **Auto-generated file:**

    ```typescript theme={null}
    // user.model.test.ts
    import { beforeAll, describe, expect, test } from "vitest";
    import { UserModel } from "./user.model";

    beforeAll(async () => {
      await UserModel.setTestData();
    });

    describe("UserModel", () => {
      test("findById", async () => {
        const user = await UserModel.findById(1);
        expect(user).toBeDefined();
        expect(user.id).toBe(1);
      });

      test("save", async () => {
        const user = await UserModel.save({
          name: "Test User",
          email: "test@example.com",
        });
        expect(user.id).toBeGreaterThan(0);
        expect(user.name).toBe("Test User");
      });
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Test Data

<AccordionGroup>
  <Accordion title="How do I prepare test data?">
    **setTestData() method:**

    ```typescript theme={null}
    // user.model.ts
    class UserModelClass extends BaseModelClass {
      static async setTestData() {
        await this.truncate();

        await this.bulkInsert([
          { id: 1, name: "John", email: "john@example.com" },
          { id: 2, name: "Jane", email: "jane@example.com" },
          { id: 3, name: "Bob", email: "bob@example.com" },
        ]);
      }
    }
    ```

    **Using in tests:**

    ```typescript theme={null}
    import { beforeAll, describe, test } from "vitest";

    beforeAll(async () => {
      await UserModel.setTestData();
      await PostModel.setTestData();
      await CommentModel.setTestData();
    });

    describe("UserModel", () => {
      test("findById", async () => {
        const user = await UserModel.findById(1);
        expect(user.name).toBe("John");
      });
    });
    ```
  </Accordion>

  <Accordion title="How do I reset DB for each test?">
    **Using beforeEach:**

    ```typescript theme={null}
    import { beforeEach, describe, test } from "vitest";

    beforeEach(async () => {
      await UserModel.setTestData();
    });

    describe("UserModel", () => {
      test("create user", async () => {
        const user = await UserModel.save({ name: "New User" });
        expect(user.id).toBeGreaterThan(3);
      });

      test("delete user", async () => {
        await UserModel.del(1);
        const user = await UserModel.findById(1);
        expect(user).toBeNull();
      });
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Model Testing

<AccordionGroup>
  <Accordion title="How do I test CRUD methods?">
    ```typescript theme={null}
    describe("UserModel CRUD", () => {
      test("findById", async () => {
        const user = await UserModel.findById(1);
        expect(user).toBeDefined();
        expect(user.id).toBe(1);
      });

      test("findMany", async () => {
        const result = await UserModel.findMany("A", { num: 10, page: 1 });
        expect(result.rows.length).toBeGreaterThan(0);
        expect(result.total).toBeGreaterThan(0);
      });

      test("save - create", async () => {
        const user = await UserModel.save({
          name: "New User",
          email: "new@example.com",
        });
        expect(user.id).toBeGreaterThan(0);
        expect(user.name).toBe("New User");
      });

      test("save - update", async () => {
        const user = await UserModel.save({
          id: 1,
          name: "Updated Name",
        });
        expect(user.id).toBe(1);
        expect(user.name).toBe("Updated Name");
      });

      test("del", async () => {
        await UserModel.del(1);
        const user = await UserModel.findById(1);
        expect(user).toBeNull();
      });
    });
    ```
  </Accordion>

  <Accordion title="How do I test Subsets?">
    ```typescript theme={null}
    describe("UserModel Subsets", () => {
      test("Subset A", async () => {
        const result = await UserModel.findMany("A", { num: 10, page: 1 });
        expect(result.rows[0]).toHaveProperty("id");
        expect(result.rows[0]).toHaveProperty("name");
      });

      test("Subset WithPosts", async () => {
        const result = await UserModel.findMany("WithPosts", { num: 10, page: 1 });
        expect(result.rows[0]).toHaveProperty("posts");
        expect(Array.isArray(result.rows[0].posts)).toBe(true);
      });

      test("Subset field existence check", async () => {
        const user = await UserModel.findById(1, "WithProfile");
        expect(user).toHaveProperty("profile");
        expect(user.profile).toBeDefined();
      });
    });
    ```
  </Accordion>

  <Accordion title="How do I test Relations?">
    ```typescript theme={null}
    describe("User Relations", () => {
      test("User -> Posts (HasMany)", async () => {
        const user = await UserModel.findById(1, "WithPosts");
        expect(user.posts).toBeDefined();
        expect(Array.isArray(user.posts)).toBe(true);
        expect(user.posts.length).toBeGreaterThan(0);
      });

      test("Post -> User (BelongsToOne)", async () => {
        const post = await PostModel.findById(1, "WithAuthor");
        expect(post.author).toBeDefined();
        expect(post.author.id).toBe(post.user_id);
      });

      test("User -> Profile (OneToOne)", async () => {
        const user = await UserModel.findById(1, "WithProfile");
        expect(user.profile).toBeDefined();
        expect(user.profile.user_id).toBe(user.id);
      });
    });
    ```
  </Accordion>
</AccordionGroup>

***

## API Testing

<AccordionGroup>
  <Accordion title="How do I test API methods?">
    ```typescript theme={null}
    describe("User API", () => {
      test("listUsers", async () => {
        const result = await UserModel.listUsers({ num: 10, page: 1 });
        expect(result.rows.length).toBeLessThanOrEqual(10);
        expect(result.total).toBeGreaterThan(0);
      });

      test("createUser", async () => {
        const user = await UserModel.createUser({
          name: "API Test User",
          email: "apitest@example.com",
        });
        expect(user.id).toBeGreaterThan(0);
        expect(user.name).toBe("API Test User");
      });

      test("updateUser", async () => {
        const user = await UserModel.updateUser(1, {
          name: "Updated via API",
        });
        expect(user.id).toBe(1);
        expect(user.name).toBe("Updated via API");
      });

      test("deleteUser", async () => {
        await UserModel.deleteUser(1);
        const user = await UserModel.findById(1);
        expect(user).toBeNull();
      });
    });
    ```
  </Accordion>

  <Accordion title="How do I test errors?">
    ```typescript theme={null}
    import { BadRequestError, NotFoundError } from "sonamu";

    describe("User API Errors", () => {
      test("createUser - duplicate email", async () => {
        await expect(
          UserModel.createUser({
            name: "Test",
            email: "john@example.com", // Already exists
          }),
        ).rejects.toThrow(BadRequestError);
      });

      test("findById - non-existent ID", async () => {
        await expect(UserModel.findById(99999)).rejects.toThrow(NotFoundError);
      });

      test("deleteUser - no permission", async () => {
        await expect(
          UserModel.deleteUser(1), // Permission check fails
        ).rejects.toThrow(ForbiddenError);
      });
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Advanced Testing

<AccordionGroup>
  <Accordion title="How do I test transactions?">
    ```typescript theme={null}
    import { Puri } from "sonamu";

    describe("Transactions", () => {
      test("transaction success", async () => {
        const wdb = UserModel.getPuri("w");

        await wdb.transaction(async () => {
          // Model.save() calls inside the callback automatically join the same transaction.
          const [userId] = await UserModel.save([{ name: "Tx User" }]);
          const [profileId] = await ProfileModel.save([{ user_id: userId, bio: "Bio" }]);

          expect(userId).toBeGreaterThan(0);
          expect(profileId).toBeGreaterThan(0);
        });
      });

      test("transaction rollback", async () => {
        const rdb = UserModel.getPuri("r");
        const before = await rdb.table("users").select({ total: Puri.count() }).first();

        const wdb = UserModel.getPuri("w");
        await expect(
          wdb.transaction(async () => {
            await UserModel.save([{ name: "Rollback User" }]);
            throw new Error("Forced error");
          }),
        ).rejects.toThrow("Forced error");

        const after = await rdb.table("users").select({ total: Puri.count() }).first();
        expect(after?.total).toBe(before?.total); // No change
      });
    });
    ```
  </Accordion>

  <Accordion title="How do I test async operations?">
    ```typescript theme={null}
    describe("Async Operations", () => {
      test("email sending", async () => {
        const result = await UserModel.sendWelcomeEmail(1);
        expect(result.success).toBe(true);
      });

      test("file upload", async () => {
        const file = {
          buffer: Buffer.from("test content"),
          originalname: "test.txt",
        } as Express.Multer.File;

        const result = await UserModel.uploadAvatar(file);
        expect(result.url).toBeDefined();
        expect(result.url).toContain("avatar");
      });

      test("external API call", async () => {
        const result = await UserModel.fetchExternalData();
        expect(result).toBeDefined();
      });
    });
    ```
  </Accordion>

  <Accordion title="How do I use Mocks?">
    ```typescript theme={null}
    import { vi } from "vitest";

    describe("Mocking", () => {
      test("external service Mock", async () => {
        // Mock EmailService.send
        const sendMock = vi.spyOn(EmailService, "send").mockResolvedValue({ success: true });

        await UserModel.sendWelcomeEmail(1);

        expect(sendMock).toHaveBeenCalledTimes(1);
        expect(sendMock).toHaveBeenCalledWith(
          expect.objectContaining({
            to: "john@example.com",
            subject: "Welcome!",
          }),
        );

        sendMock.mockRestore();
      });

      test("Date.now Mock", async () => {
        const mockDate = new Date("2025-01-01");
        vi.setSystemTime(mockDate);

        const user = await UserModel.save({ name: "Test" });
        expect(user.created_at).toEqual(mockDate);

        vi.useRealTimers();
      });
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Test Patterns

<AccordionGroup>
  <Accordion title="How do I use test.each for repeated tests?">
    ```typescript theme={null}
    describe("Validation", () => {
      test.each([
        ["john@example.com", true],
        ["invalid-email", false],
        ["", false],
        ["test@", false],
      ])("email validation: %s -> %s", async (email, expected) => {
        const isValid = await UserModel.validateEmail(email);
        expect(isValid).toBe(expected);
      });
    });

    describe("UserRole", () => {
      test.each([
        ["admin", true],
        ["user", false],
        ["guest", false],
      ])("admin permission: %s -> %s", async (role, expected) => {
        const user = await UserModel.findById(1);
        user.role = role;
        const isAdmin = await UserModel.isAdmin(user);
        expect(isAdmin).toBe(expected);
      });
    });
    ```
  </Accordion>

  <Accordion title="How do I group tests?">
    ```typescript theme={null}
    describe("UserModel", () => {
      describe("CRUD Operations", () => {
        test("create", async () => {});
        test("read", async () => {});
        test("update", async () => {});
        test("delete", async () => {});
      });

      describe("Validations", () => {
        test("email validation", async () => {});
        test("password strength", async () => {});
      });

      describe("Relations", () => {
        test("posts", async () => {});
        test("profile", async () => {});
      });
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Guidelines for writing tests">
    **DO:**

    * ✅ Each test should be independently executable
    * ✅ Initialize test data with `beforeAll` or `beforeEach`
    * ✅ Use meaningful test names
    * ✅ Test only one feature per test
    * ✅ Always test error cases

    **DON'T:**

    * ❌ Don't create dependencies between tests
    * ❌ Don't call real external services (use Mocks)
    * ❌ Don't use production DB
    * ❌ Avoid hard-coded IDs or dates
    * ❌ Don't ignore test failures
  </Accordion>
</AccordionGroup>

***

## Related Documentation

* [Naite Testing](/en/testing/using-naite)
* [Test Writing Guide](/en/testing/writing-tests)
* [Test Best Practices](/en/testing/best-practices)
* [Model Testing](/en/testing/model-testing)
