Testing Deep Dive

Testing Frameworks — The Tools by Stack

Most languages have one or two dominant testing frameworks plus a handful of supporting libraries: a runner, an assertion style, a mocking library, an integration test layer. Browser E2E has its own modern stack. The choices aren't usually controversial; the goal is knowing what each ecosystem expects so you can pick up any codebase and start writing tests on day one.

JUnitpytestJestxUnitPlaywrightCypress
← Back to Testing
Quick Facts

What a Testing Framework Provides

Basic Concepts

  • Runner: finds tests, executes them, reports results. Watch mode, parallelization, filtering.
  • Assertion style: classic assertEquals, fluent (expect(x).to.equal(y)), or behavior-style (x should equal y).
  • Mocking / fakes: built-in or via a partner library (Mockito, sinon, Moq).
  • Coverage: usually a separate tool plugged into the runner (JaCoCo, coverage.py, Istanbul/c8, dotnet-coverage).
  • Integration helpers: framework-specific harnesses for spinning up the application (Spring's MockMvc, ASP.NET WebApplicationFactory, FastAPI's TestClient).
  • E2E: a separate category — browser drivers (Playwright, Cypress, Selenium) used across all backend stacks.
By Stack

What to Use, Where

Java & Kotlin
  • JUnit 5: the default unit/integration runner. Annotations, parameterized tests, parallel execution, extension model.
  • TestNG: alternative; popular in older codebases.
  • Mockito: the standard mocking library.
  • AssertJ: fluent assertions that beat JUnit's defaults.
  • Spring Boot Test / MockMvc / TestRestTemplate: integration testing with Spring context.
  • Testcontainers: real Docker DBs/queues in tests. Now standard.
  • Spock (Groovy): expressive BDD-style; common in Kotlin/Groovy shops.
  • Kotest for Kotlin: idiomatic Kotlin assertions and runners.
.NET (C# / F#)
  • xUnit: the modern default. [Fact] and [Theory].
  • NUnit, MSTest: alternatives; still common in older codebases.
  • Moq, NSubstitute, FakeItEasy: mocking libraries.
  • FluentAssertions: readable assertions (x.Should().Be(y)).
  • WebApplicationFactory: ASP.NET Core integration testing in-process.
  • Testcontainers .NET: the .NET port of the Java original.
  • Verify (Verify.Xunit): snapshot/approval testing.
Python
  • pytest: the default. Functions as tests, powerful fixtures, vast plugin ecosystem.
  • unittest: stdlib; xUnit-style; fine for small projects but rarely chosen for new ones.
  • Hypothesis: property-based testing — generate inputs, find counterexamples.
  • Mocking: unittest.mock / pytest-mock.
  • Coverage: coverage.py + pytest-cov.
  • Frameworks-specific: Django's test client, FastAPI's TestClient, pytest-django.
  • tox / nox: matrix testing across versions and environments.
JavaScript / TypeScript
  • Vitest: the modern default for new projects. Fast, ESM-native, Jest-compatible API.
  • Jest: still dominant in many codebases. Snapshot tests, mocking, parallel by default.
  • Mocha + Chai + Sinon: the older composable stack; still common.
  • Node's built-in test runner: stdlib option since Node 18 — surprisingly capable.
  • Testing Library: the de-facto standard for React/DOM testing (@testing-library/react).
  • MSW (Mock Service Worker): intercepts fetch/XHR at the network layer for tests.
  • Playwright Component Testing: rising option for component-level browser tests.
Ruby
  • RSpec: the dominant choice. describe / it / expect BDD style.
  • Minitest: stdlib; lighter, beloved in some circles.
  • FactoryBot: test data builders.
  • Capybara: high-level browser testing DSL, often paired with Selenium or Cuprite.
  • VCR: records HTTP interactions for replay in tests.
  • Rails ships its own integration test harness tightly coupled to ActiveRecord and ActionDispatch.
Go
  • The standard library's testing package covers most cases. go test ./..., table-driven tests, subtests.
  • testify: assertions and mocks layered on top. Adds require.Equal and friends.
  • gomock: code-generated mocks from interfaces. Or write hand-rolled fakes — Go's interfaces make it easy.
  • Benchmarks: built into testing.B.
  • httptest: stdlib helpers for in-process HTTP integration tests.
  • Testcontainers Go for real DB/queue tests.
Rust
  • Built-in cargo test: tests live in #[test] functions or in tests/.
  • proptest, quickcheck: property-based testing.
  • insta: snapshot/approval testing.
  • mockall: mocking via macros.
  • criterion: serious benchmarking.
PHP
  • PHPUnit: the standard.
  • Pest: modern, expressive testing built on PHPUnit.
  • Mockery, Prophecy: mocking.
  • Laravel's Test traits are the standard for Laravel apps.
Mobile
  • iOS: XCTest (built-in), Quick + Nimble for BDD style. UI testing via XCUITest.
  • Android: JUnit + Mockito for unit tests. Instrumentation tests via Espresso. Compose UI tests for Compose UIs.
  • React Native: Jest + Testing Library; E2E via Detox or Maestro.
  • Flutter: built-in test framework + integration_test for E2E.
  • Cross-platform mobile E2E: Appium, Maestro.
Browser E2E

Cross-Stack Tooling

ToolStyleNotes
PlaywrightMulti-browser, multi-languageThe 2026 default. TS/JS, Python, Java, .NET bindings. Auto-wait, trace viewer.
CypressJS/TS, in-browser test runnerStrong UX, big ecosystem. Best when staying in the JS world.
Selenium / WebDriverW3C standard, multi-languageThe veteran. Heavier setup; mature; chosen for complex enterprise grids.
PuppeteerJS/TS, Chromium-onlyLower-level than Playwright; single-browser focus.
WebdriverIOJS/TS WebDriver clientMobile + desktop + browser via Appium and WebDriver.
Picking

Decision Quick-Reference

  • If your stack has a default, use it. Going against the grain costs documentation, hiring, and CI integration.
  • Pair the runner with one assertion library and one mocking library. Inconsistent styles make tests harder to read.
  • Adopt Testcontainers (or your stack's port) for integration tests against real DBs and queues — the embedded-engine alternatives lie.
  • Pick Playwright for new browser E2E unless the team has deep Cypress investment.
  • One framework per layer is enough. Three competing assertion libraries in one repo is a maintainability problem, not flexibility.
Continue

More on Testing