A mid-level developer is responsible for the quality of their code. Testing is the only way to prove that quality exists.
1The Red-Green-Refactor Loop
Test-Driven Development (TDD) flips the script: Write the test first (Red), write just enough code to pass (Green), then clean it up (Refactor). This ensures 100% coverage and clean design.
2Integration vs. E2E
Integration tests check how components work together. E2E tests check how the whole system works together. Mid-level devs know that E2E is for 'Critical Paths' (like Login or Checkout), not for every single button click.
3Continuous Integration (CI)
Tests shouldn't just run on your machine. They should run automatically on every Pull Request. This 'CI' pipeline is the guardian of the production environment, blocking bad code from ever being deployed.
4Step-by-Step Breakdown
Testing isn't just about 'finding bugs'. It's about 'Confidence'. It allows you to refactor and ship code at high speed without fear of breaking everything.
The Testing Pyramid: Hundreds of Unit Tests (fast), dozens of Integration Tests (medium), and a few E2E Tests (slow). This balance ensures high coverage and fast feedback.
Jest and Vitest are the industry standards for logic testing. They allow you to 'Mock' external dependencies and test individual functions in isolation.
End-to-End (E2E) testing with Playwright or Cypress simulates a real user in a real browser. It's the ultimate 'Safety Net' for your critical business flows.
What is the primary characteristic of a 'Unit Test'?
- →It tests the entire application from start to finish
- →It tests a small, isolated piece of code (like a single function) without external dependencies
- →It is run manually by a human QA tester
- →It requires a real database and a real network connection
In testing terminology, what does 'Mocking' mean?
- →Making fun of poor quality code
- →Replacing a real dependency (like an API call) with a controlled fake version for testing
- →Using a computer to simulate a human brain
- →Writing code that doesn't actually do anything
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Testing Accessibility, Not Just Function
A test suite that only checks 'does clicking the button call the handler' can pass while the button is unusable by keyboard or screen reader. Query by accessible role and name (not CSS class or test-id) so your tests fail when real users would struggle.
// Wrong: brittle, ignores a11y
screen.getByTestId('submit-btn')
// Better: mirrors how assistive tech finds it
screen.getByRole('button', { name: /submit/i })SEO Implications
- 1
E2E Tests Catch SSR/Hydration Regressions Early
A change that accidentally breaks server-side rendering (e.g. a component throwing during SSR, or content only appearing after client hydration) can tank a page's crawlability. An E2E test that checks the page renders meaningful content on first load — not just after JS runs — catches this before it ships.
Best Practices
Test Behavior, Not Implementation
Assert on what the user sees and can do, not on internal state or private methods. Tests that reach into implementation details break every time you refactor, even when the feature still works correctly.
Keep the Pyramid Balanced
If your E2E suite takes 40 minutes and your unit suite takes 4 seconds, you've inverted the pyramid. Push logic-heavy assertions down to unit tests and reserve E2E for the handful of critical user journeys.
Frequent Bugs
A test suite passes locally but fails in CI (or vice versa) because a test depends on execution order, shared mutable state, or real system time (Date.now()) instead of being fully isolated.
Reset mocks and any shared state in beforeEach/afterEach, and inject or mock time-dependent values so each test is deterministic regardless of when or in what order it runs.
Real-World Examples
Mocking an API Call in a Unit Test
A component fetches a user's profile on mount. The test needs to verify the loading and success states without making a real network request.
vi.mock('./api', () => ({
fetchProfile: vi.fn(() => Promise.resolve({ name: 'Ada' })),
}));
test('shows the user name after loading', async () => {
render(<Profile userId="1" />);
expect(await screen.findByText('Ada')).toBeInTheDocument();
});