File Structure
Tests are code too, so they require just as much attention to detail — from internal function design to external file structure.
Test Location
It's better to place stories and their associated infrastructure away from the main codebase. This creates a clear boundary between the application and its tests.
Instead of this:
project/
├── src/
│ ├── User/
│ │ ├── index.tsx
│ │ └── stories.tsx <-- Tests are placed next to the code they test
│ ├── api/
│ │ ├── userRepository.ts
│ │ └── mockUserRepository.ts <-- Mocks included here too
│ └── index.ts
└── package.json
Do this:
project/
├── src <-- Main application code
├── storyshots/ <-- Tests and associated infrastructure
│ ├── userStories.ts
│ ├── mockUserRepository.ts
│ └── index.ts
└── package.json
Baseline artifacts — logs and snapshots — can be placed in the root of the repository, as they are often used as documentation sources:
project/
├── screenshots <-- Snapshots
├── records <-- Logs
├── src
├── storyshots
└── package.json
Component Types
In tests, it's sufficient to distinguish the following components:
Environment setup component:
describe('User', [
it('allows to login', {
arrange: setup(),
/* ... */
}),
it('allows to logout', {
arrange: setup(),
/* ... */
}),
it('allows to change password', {
arrange: setup(),
/* ... */
}),
]);
// All stories set up the environment identically, so the setup function is such a component
function setup() {
/* ... */
}
Partial environment modification functions:
describe('User', [
it('allows to login', {
arrange: arrange(setup(), unauthorized()),
/* ... */
}),
it('allows to logout', {
arrange: arrange(setup(), authorized()),
/* ... */
}),
it('allows to change password', {
arrange: arrange(setup(), authorized()),
/* ... */
}),
]);
/**
* Despite shared initialization logic, stories may require partial adjustments.
* These functions belong to this category.
*/
function authorized() {
/* ... */
}
function unauthorized() {
/* ... */
}
/* ... */
Interface interaction and actor-related functions:
describe('User', [
it('allows to login', {
arrange: arrange(setup(), unauthorized()),
act: (actor) => actor.do(enterCredentials()).do(submit()),
/* ... */
}),
it('allows to logout', {
arrange: arrange(setup(), authorized()),
act: (actor) => actor.click(finder.get(button('Logout'))),
/* ... */
}),
it('allows to change password', {
arrange: arrange(setup(), authorized()),
/* ... */
}),
]);
// Functions working with actors and selectors belong to this component category
function button() {
/* ... */
}
function enterCredentials() {
/* ... */
}
function submit() {
/* ... */
}
/* ... */
Stubs:
function authorized() {
/* uses createUserStub */
}
function unauthorized() {
/* uses create401ErrorStub */
}
// POJO factories belong to the stubs category
function createUserStub() {
/* ... */
}
function create401ErrorStub() {
/* ... */
}
/* ... */
When decomposing a story, the structure may look like this:
productsStories.ts
utils // Local components
├── arrangers.ts // Local environment setup
├── setup.ts // Common environment setup
├── stubs.ts // Stubs
└── actors.ts // Interface interaction
If further decomposition is needed at the component level, files can be aggregated under a folder with the same name:
stubs/
├── createUserStub.ts
├── createRoleStub.ts
└── index.ts // Re-export file
Component Placement
The placement of elements within stories (and generally within the storyshots infrastructure) should minimize the distance between related entities:
Single file — if an element, such as a stub, is used in only one story, then they should be stored in the same file.
If a file exceeds readability thresholds, prioritization should be given to splitting it.
Shared file — if an element is used across related but different story files (same domain), it should be placed in a separate file located equidistant from the tests.
This file is typically placed at the nearest common folder level:
stories/
├── userStories.ts // Client #1
├── producStories/
│ └── removeProductsStories.ts // Client #2
├── utils/
│ └── stubs.ts // <-- Shared component
└── index.ts
Global file — if a component is used across multiple unrelated stories, it should be moved to the highest level within storyshots.
storyshots/
├── utils/
│ ..... // Global components used across unrelated stories.
└── stories/
├── userStories.ts
└── producStories/
├── utils // Local components
│ ..... // Used only within producStories folder
├── productRemoveStories.ts
└── productsStories.ts
Following this methodology makes it easy to understand a component’s responsibility — the higher it appears in the tree, the more clients depend on it, and the harder it becomes to modify directly.