How to organize test files, structure tests with describe and it blocks, write descriptive test names, apply the Arrange-Act-Assert pattern, and choose between co-located and centralized test directories.
Co-located tests (Button.test.tsx next to Button.tsx) are preferred in modern projects because they reduce friction and make missing tests visible.
Use describe blocks to group related tests by feature or method, and it/test blocks for individual behaviors. Nesting creates a readable hierarchy.
Structure every test in three phases: set up preconditions (Arrange), execute the behavior (Act), and verify the outcome (Assert) for consistent readability.
Test names should describe expected behavior, not implementation. A new team member should understand what is being tested without reading the test body.
Well-structured tests are easy to read, maintain, and debug. Test organization is one of the first things interviewers notice because it reflects overall engineering discipline. Both Jest and Vitest use the same describe/it/test API.
There are two main approaches to organizing test files:
Co-located tests place test files next to the code they test:
src/
components/
Button.tsx
Button.test.tsx
utils/
format.ts
format.test.tsAdvantages: Easy to find tests for any file, encourages testing as part of development, makes it obvious when a file lacks tests.
Centralized test directory keeps all tests in a separate __tests__ folder:
src/
components/
Button.tsx
utils/
format.ts
__tests__/
components/
Button.test.tsx
utils/
format.test.tsAdvantages: Cleaner source directories, easier to configure separate tooling for tests, matches some organizational preferences.
Most modern projects prefer co-located tests because they reduce friction and make test gaps visible.
describe groups related tests. it (or test -- they are aliases) defines individual test cases:
describe('ShoppingCart', () => {
describe('addItem', () => {
it('adds the item to the cart', () => { ... });
it('increases the total by the item price', () => { ... });
it('handles duplicate items by incrementing quantity', () => { ... });
});
describe('removeItem', () => {
it('removes the item from the cart', () => { ... });
it('throws an error when the item is not in the cart', () => { ... });
});
});describe blocks can be nested to create a hierarchy that mirrors the code's structure or the behavior being tested.
Good test names describe the expected behavior, not the implementation:
it('should ...') or it('returns/throws/renders ...') formatGood: it('displays an error message when the email is invalid')
Bad: it('calls setError with validation result when regex fails')
Every test should have three clearly separated phases:
it('applies a 10% discount for orders over $100', () => {
// Arrange: Set up test data and preconditions
const cart = createCart();
cart.addItem({ name: 'Laptop', price: 150 });
// Act: Execute the behavior under test
const total = cart.calculateTotal();
// Assert: Verify the expected outcome
expect(total).toBe(135);
});Some teams add comments to mark each phase, others rely on whitespace separation. The key is consistency within the project.
Each test should ideally verify one behavior. Multiple assertions are acceptable when they verify different aspects of the same behavior, but if a test needs many assertions, it may be testing too much.
Common conventions:
Component.test.tsx (most common)Component.spec.tsx (BDD-influenced)__tests__/Component.tsx (directory-based)Jest and Vitest auto-discover files matching **/*.test.{js,ts,jsx,tsx} and **/*.spec.{js,ts,jsx,tsx} by default.
it.skip() / xit(): Temporarily skip a testit.only() / fit(): Run only this test (useful for debugging)describe.skip() / describe.only(): Skip or focus entire groupsNever commit .only to source control -- it silently skips other tests. Use ESLint rules like eslint-plugin-jest or eslint-plugin-vitest to prevent this.
Fun Fact
The describe/it naming convention comes from RSpec (Ruby, 2005), which was designed to read like English: 'describe ShoppingCart, it adds items to the cart'. This BDD-inspired syntax was adopted by Mocha, then Jasmine, then Jest, and finally Vitest.