{"post":{"slug":"unit-testing-explained-2026","title":"Unit testing explained in 2026: write tests that actually help","excerpt":"Most developers know they should write unit tests but struggle with what to test, how to structure them, and why their tests keep breaking. Here is the 2026 guide.","category":"code","date":"March 19, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">U</span>nit testing is the practice every team claims to do and almost none does consistently well. The tests that get written break on every refactor, take minutes to run, and require a running database. None of those are unit tests — they are poorly-structured integration tests with the wrong scope. Good unit tests are fast, isolated, and tell you exactly what broke. Here is what that looks like in 2026.\n\n## What changed in 2026\n\n- **Vitest replaced Jest** as the default JS test runner for new projects — it is 3–5× faster via native ESM, requires zero transform config, and shares Vite's configuration.\n- **pytest remains dominant in Python**, but `pytest-asyncio` graduated to stable, making async test support first-class.\n- **AI-assisted test generation** (GitHub Copilot, Cursor) accelerates writing the boilerplate, but human review is still required — generated tests tend to test implementation not behaviour.\n- **Coverage as a gate** is declining; mutation testing tools (Stryker, mutmut) that measure test quality over quantity are gaining adoption.\n\n## What is a unit?\n\nA \"unit\" is a single function, method, or class. A unit test:\n\n- Exercises one behaviour (not one method — a single method may have multiple behaviours)\n- Runs in memory with no real I/O\n- Completes in under 10 ms\n- Is deterministic (same result every run)\n\nIf your test starts a server, connects to a database, or reads a file, it is not a unit test. That is fine — integration tests have their place — but they belong in a different suite.\n\n## Arrange-Act-Assert\n\nEvery good test follows AAA:\n\n```ts\n// Vitest / Jest — TypeScript\nimport { describe, it, expect } from \"vitest\";\nimport { calculateDiscount } from \"./pricing\";\n\ndescribe(\"calculateDiscount\", () => {\n  it(\"applies 20% discount for premium users\", () => {\n    // Arrange\n    const user = { tier: \"premium\" };\n    const price = 100;\n\n    // Act\n    const result = calculateDiscount(price, user);\n\n    // Assert\n    expect(result).toBe(80);\n  });\n});\n```\n\nEach test should have exactly one reason to fail. If an assertion fails, the test name should tell you what broke.\n\n## The testing pyramid\n\n| Layer | Speed | Count | Scope |\n| --- | --- | --- | --- |\n| Unit | ~1 ms | Most (~70%) | One function/class, no I/O |\n| Integration | ~100 ms–1 s | Some (~20%) | Multiple classes, real DB or HTTP |\n| End-to-end | ~5–30 s | Few (~10%) | Full stack, browser or API |\n\nA fast suite needs the pyramid shape: many unit tests, fewer integration tests, a handful of E2E tests. Inverting it — lots of E2E — makes CI slow and flaky.\n\n## What to test\n\n| Worth testing | Not worth testing |\n| --- | --- |\n| Business logic with conditions or calculations | Getters/setters with no logic |\n| Error paths and edge cases | Private methods (test through the public API) |\n| Parsing / transformation functions | Framework routing boilerplate |\n| Boundary conditions (empty, max, negative) | Auto-generated code |\n\nTest behaviour: \"when input is X, output is Y.\" Do not test that a particular internal function was called.\n\n## Python example with pytest\n\n```python\n# test_pricing.py\nimport pytest\nfrom pricing import calculate_discount\n\ndef test_premium_user_gets_20_percent_off():\n    assert calculate_discount(100, tier=\"premium\") == 80\n\ndef test_free_user_gets_no_discount():\n    assert calculate_discount(100, tier=\"free\") == 100\n\ndef test_negative_price_raises():\n    with pytest.raises(ValueError, match=\"price must be positive\"):\n        calculate_discount(-1, tier=\"free\")\n```\n\nEach test function is one behaviour. The test name is the specification.\n\n## How to pick what to test first\n\n1. **Start with pure functions** — no mocks, easy to write.\n2. **Cover the happy path first**, then add edge cases.\n3. **Write the test for any bug you fix** — prevent regression.\n4. **Prioritise code that is expensive to break** — billing, auth, data transformations.\n\n## Common mistakes\n\n**Testing implementation.** Asserting that `userService.db.find` was called. When you refactor, the test breaks even though behaviour is identical. Test outcomes, not calls.\n\n**One test per method.** Some methods have many behaviours; some code paths need several tests. Name by behaviour, not by method.\n\n**Giant setup.** If your `beforeEach` is 50 lines, the test is too coupled to infrastructure. Move that work to a factory or builder.\n\n**No test for the unhappy path.** Most bugs live in error branches. Test what happens when the input is invalid, the service is down, or the data is empty.\n\n## What to skip\n\n- **100% line coverage as a goal** — it measures lines touched, not decisions verified. A test that executes every line without asserting anything passes coverage and catches nothing.\n- **Testing third-party library behaviour** — trust the library's own tests.\n- **Mocking everything** — excessive mocking creates tests that test your mocks, not your code. See [Mocking explained in 2026](/blog/mocking-explained-2026).\n\n## FAQ\n\n**How many assertions per test?**\nOne logical assertion is the rule of thumb. Multiple `expect()` calls are fine if they all verify the same outcome. If they verify different outcomes, split the test.\n\n**Should I write tests before or after the code?**\nBoth work. TDD (tests first) forces good design and clear interfaces. Tests after work when you are exploring. The important thing is that they get written.\n\n**How do I test async code?**\nVitest and pytest-asyncio both support `async` test functions natively. Use `await` inside the test body.\n\n**What is mutation testing?**\nMutation testing tools (Stryker for JS, mutmut for Python) automatically introduce bugs into your code and check whether your tests catch them. A mutation score above 80% is a meaningful quality signal.\n\n## Where to go next\n\nSee [Mocking explained in 2026](/blog/mocking-explained-2026), [Integration testing explained in 2026](/blog/integration-testing-explained-2026), and [Dependency injection explained in 2026](/blog/dependency-injection-explained-2026).","hue":322,"metaTitle":"Unit Testing Explained 2026: Write Tests That Actually Help","metaDescription":"Practical 2026 guide to unit testing. What to test, how to structure tests with AAA, the testing pyramid, and the mistakes that make test suites brittle.","keywords":["unit testing explained 2026","how to write unit tests 2026","arrange act assert pattern","unit test vs integration test","testing pyramid 2026","jest unit tests 2026","pytest unit tests 2026","what to unit test","test driven development 2026","unit testing best practices"],"tldr":["**A unit test checks one behaviour of one unit in isolation** — it runs in milliseconds and needs no real network or database.","**Test behaviour, not implementation** — tests that assert on internal method calls break on every refactor.","**Arrange-Act-Assert (AAA) is the universal structure** — every good test follows it.","**Aim for ~70% unit tests, ~20% integration, ~10% end-to-end** — the testing pyramid keeps the suite fast.","**Skip:** testing framework internals, getters/setters, and private methods — test through the public API."],"readTime":"4 min read","wordCount":943},"related":[{"slug":"how-to-write-unit-tests-2026","title":"How to write unit tests in 2026: patterns, coverage, and what to actually test","excerpt":"Unit tests are fast, focused, and the foundation of a reliable test suite. This 2026 guide covers what to test, how to structure tests, and the patterns that make tests useful rather than just green.","category":"code","date":"March 2, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":319},{"slug":"unit-vs-integration-testing-2026","title":"Unit vs Integration Testing Explained 2026: Where Each Pays Off","excerpt":"Unit tests check one piece in isolation; integration tests check pieces working together. Here is the 2026 guide to the difference, the right balance, and how to avoid slow, brittle test suites.","category":"code","date":"July 1, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":150},{"slug":"how-to-write-tests-2026","title":"How to Write Tests in 2026: a practical starter guide","excerpt":"Tests let you change code without fear. This 2026 guide covers what to test, the arrange-act-assert pattern, and how to start without testing everything.","category":"code","date":"June 16, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":195}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"the-two-minute-rule-explained-2026","title":"The two-minute rule explained in 2026: kill small tasks before they pile up","excerpt":"The two-minute rule says if a task takes less than two minutes, do it now instead of scheduling it. Here is how to apply it to work and life in 2026.","category":"guides","date":"March 19, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":95},"older":{"slug":"how-to-use-elevenlabs-2026","title":"How to use ElevenLabs in 2026: realistic AI voices for real projects","excerpt":"ElevenLabs produces the most natural AI speech available in 2026. Here is how to use voice cloning, Speech Synthesis, and the API for production-quality audio.","category":"ai","date":"March 18, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":198}}