UNPKG

test-fixture-factory

Version:

A minimal library for creating and managing test fixtures using Vitest, enabling structured, repeatable, and efficient testing processes.

288 lines (207 loc) 8.63 kB
# test-fixture-factory `test-fixture-factory` is an NPM package designed to streamline the creation and management of test fixtures within TypeScript projects using **Vitest** [Test Contexts](https://vitest.dev/guide/test-context.html). This library leverages structured factory functions to generate test data and manage the lifecycle of these fixtures efficiently, making your tests more organized, repeatable, and maintainable. ## Table of Contents - [Installation](#installation) - [Features](#features) - [Why Use test-fixture-factory?](#why-use-test-fixture-factory) - [Getting Started](#getting-started) - [Usage](#usage) - [Defining a Factory](#defining-a-factory) - [Using Factories in Tests](#using-factories-in-tests) - [Destroying Resources](#destroying-resources) - [API](#api) - [License](#license) ## Installation To add `test-fixture-factory` to your project, run: ```bash npm add --save-dev test-fixture-factory ``` Ensure you have `vitest` set up as your test runner since this package is designed to work with it. ## Features - **Define Factories:** Easily define factories for creating fixtures with dependencies and attributes. - **Lifecycle Management:** Automatically manage the creation and destruction of test resources. - **Integration with Vitest:** Seamlessly integrates with Vitest's testing functions. Here’s a brief example illustrating a feature: ```typescript import { defineFactory } from 'test-fixture-factory'; const companyFactory = defineFactory(async ({}, attrs: { name: string }) => { const company = await prisma.company.create({ name: attrs.name, }); return { value: company, destroy: async () => { await prisma.company.delete({ where: { id: company.id } }); }, }; }); ``` ## Why Use test-fixture-factory? Testing with accurate and meaningful data is crucial for ensuring that your code behaves as expected. `test-fixture-factory` simplifies the process of setting up data for tests by providing: - **Simplicity:** Avoid boilerplate code and manually setting up test data. - **Consistency:** Ensure that each test runs with predictable and manageable data setup. - **Isolation:** Prevent test case interference by cleaning up after tests automatically. - **Robustness:** Handle complex dependencies among fixtures with ease. ## Getting Started ### Requirements - [Node.js (v20 or later)](https://nodejs.org/) - [Vitest](https://vitest.dev/) (may also work with [Playwright](https://playwright.dev/)) ### Setup 1. Install the package using npm: ```bash npm install test-fixture-factory ``` 2. Ensure `vitest` is installed and configured in your project. ## Usage ### Defining a Factory To define a factory, use the `defineFactory` function. A factory is a function that takes dependencies and attributes to produce a test value. #### Without any Dependencies ```typescript import { defineFactory } from 'test-fixture-factory'; import type { Company } from 'prisma' type Dependencies = {}; // Alternatively as `Record<string, unknown>` type Attributes = { name: string } const companyFactory = defineFactory( async ({}: Dependencies, attrs: Attributes): Company => { const company = await prisma.company.create({ name: attrs.name, }); return { value: company, destroy: async () => { await prisma.company.delete({ where: { id: company.id } }); } }; }); export const useCompany = companyFactory.useValueFn; export const useCreateCompany = companyFactory.useCreateFn; ``` #### With Dependencies ```typescript import { defineFactory } from 'test-fixture-factory'; import type { Company, User } from 'prisma' type Dependencies = { company: Company } type Attributes = { name: string email: string } const userFactory = defineFactory( async ({ company }: Dependencies, attrs: Attributes): User => { const user = await prisma.user.create({ company: company.id, name: attrs.name, email: attrs.email, }); return { value: user, destroy: async () => { await prisma.user.delete({ where: { id: user.id } }); } }; }); export const useUser = userFactory.useValueFn; export const useCreateUser = userFactory.useCreateFn; ``` ### Using Factories in Tests Factories can be used to create or directly retrieve values in test functions. Dependencies such as `company` in the example below are automatically passed into factory functions through Vitest's [Fixture Initialization](https://vitest.dev/guide/test-context.html#fixture-initialization). ```typescript import { test as anyTest, expect } from 'vitest'; import { useCompany } from './factories/company.js' import { useCreateUser } from './factories/user.js' const test = anyTest.extend({ company: useCompany({ name: 'Crinkle' }), createUser: useCreateUser() }); test('it creates a user', async ({ company, createUser }) => { const alice = await createUser({ name: 'Alice', email: 'alice@example.com' }); const bob = await createUser({ name: 'Bob', email: 'bob@example.com' }); expect(alice).toEqual({ id: expect.any(Number), companyId: company.id, name: 'Alice', email: 'alice@example.com', }); expect(bob).toEqual({ id: expect.any(Number), companyId: company.id, name: 'Bob', email: 'bob@example.com', }); /** * Note: once this test has completed, alice and bob will both be removed * from the database. */ }); ``` ### Reusing Fixtures ```typescript import { test as anyTest, expect } from "vitest"; import { InferFixtureValue } from "test-fixture-factory"; import { useCompany } from "./factories/company.js"; import { useCreateUser } from "./factories/user.js"; const createFixtures = async ({ createUser, }: { createUser: InferFixtureValue<typeof useCreateUser>; }) => { const alice = await createUser({ name: "Alice", email: "alice@example.com" }); const bob = await createUser({ name: "Bob", email: "bob@example.com" }); return { alice, bob }; }; const test = anyTest.extend({ company: useCompany(), createUser: useCreateUser(), }); test("tests something", async ({ company, createUser }) => { const { alice, bob } = await createFixtures({ createUser }); // ... }); test("tests something else", async ({ company, createUser }) => { const { alice, bob } = await createFixtures({ createUser }); // ... }); ``` ### Destroying Resources Factories ensure resources are destroyed properly after use. This avoids any residual data that might affect subsequent tests. Each factory can optionally specify a `destroy` function to clean up resources. Since `destroy` is called in the reverse order of fixture definition, this should avoid any dependency conflicts (e.g. mandatory foreign key relationships in database tables). ### Resource Cleanup Control There are two ways to control whether resources are automatically cleaned up after tests: #### Environment Variable You can set the `TFF_SKIP_DESTROY` environment variable to any non-empty value to globally disable resource cleanup: ```bash TFF_SKIP_DESTROY=1 npm test ``` This is useful during development when you want to inspect the state of resources after tests run. #### Per-Factory Options You can also control cleanup behavior at the factory level using the `shouldDestroy` option: ```typescript // Disable cleanup for a specific useValueFn call const test = anyTest.extend({ company: useCompany({}, { shouldDestroy: false }), createCompany: useCreateCompany({ shouldDestroy: false }) }); ``` The `shouldDestroy` option: - Defaults to `true` unless `TFF_SKIP_DESTROY` is set - Can be passed to both `useValueFn` and `useCreateFn` - Takes precedence over the `TFF_SKIP_DESTROY` environment variable This granular control is useful when: - You need to keep certain resources for inspection after specific tests - You want to manage cleanup manually - You're debugging specific test scenarios ## API ### `defineFactory(factoryFn)` **Parameters:** - `factoryFn`: A function that produces the fixture, taking dependencies and attributes, and returns an object containing the value and an optional `destroy` function. **Returns:** - The `defineFactory` function returns the same `factoryFn` that was passed in. However, this function now has extra methods available on it: `useCreateFn` and `useValueFn`. - **`useCreateFn`**: Provides a function to create instances of the fixture with managed lifecycle. - **`useValueFn(attrs)`**: Directly retrieves a fixture value, managing the lifecycle automatically. ## License This package is [MIT licensed](LICENSE).