Automated tests are what let you change code with confidence. A good test suite means you can refactor, upgrade a dependency, or ship a feature on a Friday afternoon — and know within minutes whether you broke something, rather than finding out from users the next day.
If Cartara flagged this in your diff, you likely added or modified test files, a testing configuration, or a CI step that runs tests.
Why Tests Matter
The honest case for testing isn't "bugs are bad" — everyone knows that. It's that tests let you move fast safely. Without them, every change carries risk that can only be verified manually, which slows you down or leads to breakage you catch too late.
Tests are also documentation that can't go stale: a test describes what code is supposed to do, and fails loudly if that stops being true.
The Test Pyramid
The test pyramid is a model for how much of each kind of test to write. The shape matters — many fast, cheap tests at the bottom; few slow, expensive ones at the top.
```
/\ E2E / UI tests ← few, slow, high confidence
/
/----\ Integration tests ← some, medium speed
/
/--------\ Unit tests ← many, fast, cheap
```
Unit Tests (The Base)
Unit tests test one small piece — a single function or component — in isolation. They run in milliseconds, make up the bulk of a healthy suite, and pinpoint exactly where a failure is.
```ts
// A unit test for a price calculation function
test('applies 10% discount correctly', () => {
expect(calculatePrice(100, 0.10)).toBe(90);
});
```
Integration Tests (The Middle)
Integration tests check that pieces work together — your code talking to a real database, or two services exchanging data. Slower and fewer than unit tests, but they catch bugs that live in the seams between components.
End-to-End (E2E) Tests (The Top)
E2E tests drive the whole application like a real user would — open the browser, click login, fill a form, check the result. Highest confidence, but slowest and most fragile. Keep these focused on your most critical user journeys (signup, payment, the core action your app exists to do).
The Anti-Pattern: The Ice Cream Cone
When teams skip unit tests and rely mostly on slow E2E tests, they get the pyramid inverted: slow pipelines, flaky failures, painful debugging. If your test suite takes 20 minutes, this is usually why.
What You'll See in Your Code
Test files typically appear in a __tests__/ directory, next to the files they test, or in a test/ or e2e/ folder:
```
src/
utils/
calculatePrice.ts
calculatePrice.test.ts ← unit test sits beside the code
routes/
orders.ts
orders.test.ts
e2e/
checkout.spec.ts ← E2E tests for critical flows
```
A typical test file using Vitest or Jest:
```ts
import { describe, test, expect } from 'vitest';
import { calculatePrice } from './calculatePrice';
describe('calculatePrice', () => {
test('applies discount correctly', () => {
expect(calculatePrice(100, 0.10)).toBe(90);
});
test('returns full price with no discount', () => {
expect(calculatePrice(100, 0)).toBe(100);
});
});
```
A test config file (vitest.config.ts, jest.config.js) tells the test runner where to find tests and how to run them.
Where Tests Run
Tests act as gates in your CI/CD pipeline, ordered fast-to-slow so failures surface early:
- On every commit / PR — linting and unit tests (seconds to a couple of minutes)
- Before merge — integration tests against real dependencies
- On staging — E2E tests and accessibility checks
- After production deploy — smoke tests confirming the release is healthy
The key principle: fast feedback. Keep the commit-stage suite under a few minutes so developers don't lose focus. Cache dependencies and run tests in parallel to keep it quick.
Common Test Tools (JavaScript/TypeScript)
| Tool | Role | Notes |
|---|---|---|
| Vitest | Unit + integration | Fast, modern, great developer experience |
| Jest | Unit + integration | Mature, huge ecosystem, well-documented |
| Playwright | End-to-end | Multi-browser, fast, good for critical flows |
| Cypress | End-to-end | Excellent interactive debugging experience |
| Testing Library | Component testing | Tests UI the way users interact with it |
A common modern setup: Vitest for unit and integration tests, Playwright for E2E.
Flaky Tests: The Silent Killer
A flaky test passes sometimes and fails sometimes without the code changing — usually due to timing issues, test order, or shared state. They're worse than no test, because they train teams to ignore red builds. Once people start saying "that test is just flaky, re-run it," the whole suite loses its authority.
Treat flakiness as a real bug: fix it, or quarantine the test until you can.
Code Coverage
Coverage measures the percentage of your code that runs during tests. Useful for finding blind spots, but a terrible target to maximise.
The trap: 100% coverage doesn't mean your code works — it means every line ran, not that the outcomes were checked. Test the code that matters most (the parts where a bug would hurt), not just the parts easiest to hit.
Getting Started on a Small Team
Don't try to build a full pyramid on day one:
- Write unit tests for your core business logic first — the calculations and rules where a bug really costs you.
- Add a handful of E2E tests for your most critical flows.
- Wire them into CI so they run automatically and block merges when they fail.
- Add regression tests as you fix bugs — turn each real bug into a permanent guard.
A few tests that run reliably and cover what matters beat a large suite you don't trust.