Loading...
Loading...
[Pragmatic DDD Architecture] Guide for creating tests. Use when creating unit tests, integration tests, or understanding test conventions. Covers our tightly coupled stack: Vitest (unit, integration, ui projects), file naming, transactional database integration tests (txTest) with testcontainers/node-postgres/drizzle, mock patterns (createMock*RepoWithAssertions), and neverthrow Result assertions.
npx skill4agent add leif-sync/pragmatic-ddd testingneverthrowassert(result.isOk())node:assert.value.errorexpect(result.error).toBeInstanceOf(SomeDomainError);vitestexpect.hasAssertions()import { expect, test, describe, vi } from "vitest";
import { CreateFolder, CreateFolderParams } from "./createFolder";
import { FolderRepository } from "../domain/interfaces/folderRepository";
import { ok, err } from "neverthrow";
import assert from "node:assert";
// Example Mock Factory Pattern
function createMockFolderRepo(p?: Partial<FolderRepository>): FolderRepository {
return {
create: vi.fn(async () => ok(undefined)),
exists: vi.fn(async () => ok(false)),
...p,
};
}
describe(CreateFolder.name, () => {
test("successfully creates a folder", async () => {
// 1. Arrange
const mockRepo = createMockFolderRepo({
create: vi.fn(async (params) => {
expect(params.folder.folderName.getValue()).toBe("Test Folder");
return ok(undefined);
})
});
const useCase = new CreateFolder({ folderRepo: mockRepo, ... });
// 2. Act
const result = await useCase.execute({ ... });
// 3. Assert Narrowing
assert(result.isOk()); // Narrows result into Ok
expect(result.value.folderId).toBeDefined();
});
});drizzle-ormneverthrowtxTesttxTesttestNodePgDatabaseTransactionRollbackErrorimport { describe, expect } from "vitest";
import { txTest } from "@/shared/test/integrationUtils";
import { PostgresFolderRepository } from "./postgresFolderRepository";
// ⚠️ No importar el 'test' estandar de vitest cuando se usa txTest
// Usa type Tx = NodePgDatabase para helpers de creacion si existen
describe(PostgresFolderRepository.name, () => {
txTest("successfully creates a folder and returns ok", async (tx) => {
// txTest wraps the vitest test directly.
// Uses the isolated database connection 'tx'
const repo = new PostgresFolderRepository({ db: tx });
// Pass the transaction down to actual methods
const result = await repo.create({ tx, folder, workspaceId });
expect(result.isOk()).toBe(true);
const exists = await repo.exists({ folderId: folder.getId() });
assert(exists.isOk()); // with node:assert
expect(exists.value).toBe(true);
}); // txTest automatically rolls back via tx.rollback() internally
});references/unit-tests.mddescribe`${ClassName.name} use case`describecreateMock*RepoWithAssertions()@/shared/test/utils"This method should not be called"createNonCallableMockRepos()toBeInstanceOftoEqual`should return ${ErrorClass.name} error ...``should return ${RepositoryError.name} error when ${RepoClass.name} fails`references/integration-tests.mddescribeClassName.nametxTest@/shared/test/integrationUtilstesttxTesttxtype Tx = NodePgDatabasedescribetoEqualtoHaveLength(n){ tx, ... }setupParentinsertX// UserRepository has no parent dependencies, so there's no setupParent.
// FolderRepository's parent is workspace — setupWorkspace creates user + workspace internally.
async function setupWorkspace({ tx }: { tx: Tx }): Promise<{ workspaceId: UUID }> {
const userId = UUID.random();
const workspaceId = UUID.random();
await tx.insert(users).values({ id: userId.getValue(), ... });
await tx.insert(workspaces).values({ id: workspaceId.getValue(), ownerId: userId.getValue(), ... });
return { workspaceId }; // userId never leaves — callers don't need it
}setupSubjectawait setupWorkspace({ tx })async function setupWorkspace(p: {
tx: Tx;
userId?: UUID; // optional — if absent, a fresh user is created automatically
}): Promise<{ workspace: Workspace; userId: UUID }> {
const userId = p.userId ?? (await setupUser({ tx: p.tx })).userId;
const workspace = buildWorkspace({ ownerId: userId });
await insertWorkspace({ tx: p.tx, workspace });
return { workspace, userId };
}
// Noise (isolated chain):
await setupWorkspace({ tx });
// Siblings under the same user (for testing pagination, list isolation, etc.):
const { userId, workspace: workspace1 } = await setupWorkspace({ tx });
const { workspace: workspace2 } = await setupWorkspace({ tx, userId });
const { workspace: workspace3 } = await setupWorkspace({ tx, userId });buildXinsertX// ✓
function buildFolder({ name }: { name?: FolderName } = {}): Folder { ... }
async function insertFolder({ tx, workspaceId, folder }: { tx: Tx; workspaceId: UUID; folder: Folder }): Promise<void> { ... }
await insertFolder({ tx, workspaceId, folder });
// ✗
async function insertFolder(tx: Tx, workspaceId: UUID, folder: Folder): Promise<void> { ... }
await insertFolder(tx, workspaceId, folder); // easy to swap args silently// ✓ — intent is clear at a glance
const nonExistentWorkspaceId = UUID.random();
const { userId: userWithoutWorkspace } = await setupUser({ tx });
const { workspace: someoneElseWorkspace } = await setupWorkspace({ tx });
// ✗ — forces reader to infer meaning from usage
const workspaceId = UUID.random();
const userId2 = UUID.random();existsexistsworkspaceId + userIdfolderId + workspaceId// ✓ Two distinct tests:
"should return false when workspace exists but does not belong to the user" // exists globally, wrong owner
"should return false when workspace ID does not exist in the system" // never existed
// ✗ One combined test that only proves one of the two:
"should return false when workspace not found"assert().value.error// Ok path
assert(result.isOk());
expect(result.value).toEqual(expectedEntity);
// Err path
assert(result.isErr());
expect(result.error).toBeInstanceOf(SpecificError);
expect(result.error).toEqual(new SpecificError({ ... }));UUID.random()WorkspaceName.random()DomainName.random()const itemsPerPage = PositiveInteger.from(10)._unsafeUnwrap({
withStackTrace: true,
});PositiveInteger.one()NonNegativeInteger.zero()@/*type// Unit tests
import { expect, test, describe, assert, vi } from "vitest";
import { err, ok } from "neverthrow";
import { createMockWorkspaceRepoWithAssertions } from "@/shared/test/utils";
// Integration tests — do NOT import 'test' from vitest
import { assert, describe, expect } from "vitest";
import { txTest } from "@/shared/test/integrationUtils";RepositoryErrorexiststrueexistsfalseexistsfalseRepositoryError