Testing Concepts
JUnit 5, Mockito, and Spring testing — core concepts explained in depth.
Principles, Strategy and End-to-End
Software Testing Principles
Unit vs. integration vs. system vs. acceptance testing, and black-box vs. white-box testing — the vocabulary behind how a test suite is organized, from JUnit in Action, Third Edition, Ch. 5.
Selenium Browser Testing
Driving a real browser through the WebDriver interface — element lookup, cross-browser parameterized tests, and the Page Object model — with notes on what changed from the book's Selenium 3 to today's Selenium 4, from JUnit in Action, Third Edition, Ch. 15.
Test-Driven Development
The red-green-refactor cycle — write a failing test first, add the smallest code to pass, then refactor safely — driving design with JUnit 5, from JUnit in Action, Third Edition, Ch. 20.
BDD with Cucumber
Writing behavior specifications in business-readable Gherkin (Given/When/Then) and binding them to Java step definitions with Cucumber — with today's io.cucumber + JUnit 5 Platform setup replacing the book's obsolete info.cukes/@RunWith(Cucumber.class), from JUnit in Action, Third Edition, Ch. 21.
The Test Pyramid Strategy
How to distribute tests across levels — many fast unit tests at the base, fewer integration/system/acceptance tests toward the top — what to test at each level, and which tool fits each, from JUnit in Action, Third Edition, Ch. 22.
Testing by Code Type: The Four Quadrants
A map of production code by complexity/domain significance and collaborator count — where unit testing pays off best, where it's a waste, and how the Humble Object pattern fixes the code that's genuinely hard to test.
JUnit 5
JUnit 5 Fundamentals
The JUnit 5 test lifecycle, assertions vs. assumptions, nested/tagged tests, and parameterized/dynamic tests — from JUnit in Action, Third Edition, Ch. 2.
JUnit 5 Architecture
How Platform, Jupiter, and Vintage fit together to run new and legacy tests side by side, plus a short JUnit 4-to-5 migration reference — from JUnit in Action, Third Edition, Ch. 3.1/3.3 (and a short Ch. 4 note).
JUnit 5 Extension Model
The five extension points (conditional execution, lifecycle callbacks, parameter resolution, exception handling, instance postprocessing) behind @ExtendWith — the same mechanism MockitoExtension and SpringExtension use — from JUnit in Action, Third Edition, Ch. 14.
Reusing Test Fixtures, Parameterized Tests, and Assertion Libraries
Why constructor/@BeforeEach-based fixture reuse couples tests to each other and hurts readability, why private factory methods (Object Mother) are the better default, when to reach for @ParameterizedTest and @MethodSource, and how AssertJ's fluent style avoids the expected/actual argument-order trap.
Writing Good Unit Tests
Test Quality and Testable Code
Measuring coverage with JaCoCo, writing testable code (Law of Demeter, composition over conditionals), and how TDD/BDD/mutation testing fit the development cycle — from JUnit in Action, Third Edition, Ch. 6.
The Four Pillars of a Good Unit Test
Protection against regressions, resistance to refactoring, fast feedback, and maintainability — the four attributes that judge any automated test, and why no test can maximize all four at once.
Output-Based, State-Based, and Communication-Based Testing
The three ways a unit test can verify behavior — checking a returned output, checking resulting state, or checking a call made to a collaborator — and why output-based testing produces the highest-quality tests but only works for side-effect-free code, leaving state-based testing as the reasonable default and communication-based testing (mocks) as the rare exception.
Functional Architecture: Separating Decisions from Side Effects for Testability
Learn the functional-core/imperative-shell pattern for pulling business decisions out of side-effect-laden code so the decision logic becomes trivially output-based testable with zero mocks, and understand the real costs — a shell that still needs integration tests, allocation overhead from immutability in hot paths, and domain logic that can't always be cleanly separated this way.
Unit Testing Anti-Patterns: Private Methods, Leaked Domain Knowledge, and Time
Why testing private methods, exposing private state, reimplementing the SUT's logic in a test, polluting production code with test-only switches, mocking concrete classes, and calling the system clock directly all trace back to the same root cause — from Khorikov, Chapter 11 "Unit testing anti-patterns".
Structuring and Naming Unit Tests: AAA and Plain-English Names
The AAA pattern's real rules beyond "use three sections": never write multiple act sections or an if statement in a test, treat a multi-line act section as a production-code smell not a test problem, and reject rigid MethodUnderTest_Scenario_Result naming for plain-English test names.
Test Doubles and Mocking
Test Doubles: Stubs and Mocking
Stubs vs. mocks as two strategies for faking a collaborator, and how to create and verify mocks with Mockito — from JUnit in Action, Third Edition, Ch. 7–8.
Classical vs. London Schools of Unit Testing
Two genuinely different definitions of test "isolation" — mock every collaborator (London) vs. mock only shared/volatile dependencies (classical) — and why they disagree about what a unit even is.
Managed vs. Unmanaged Dependencies in Integration Tests
Why an integration test should hit a real database (a managed dependency) but mock an SMTP server or message bus (an unmanaged dependency) — and why mocking a managed dependency quietly breaks resistance to refactoring.
Observable Behavior vs. Implementation Details: Why Mocks Make Tests Fragile
A mock (outgoing command) and a stub (incoming query) answer different questions, and asserting interactions with a stub is a classic overspecification mistake. The deeper principle: mocking itself doesn't cause fragile tests — mocking an interaction that's an implementation detail (intra-system, no client goal behind it) rather than observable behavior (a call that crosses the application boundary and stays visible outside it) does. The fix isn't avoiding mocks, it's mocking only at true system boundaries.
Mocking Best Practices: Integration Tests Only, Spies, and Types You Own
Concrete, sometimes counter-intuitive rules for using mocks well beyond the mock-vs-stub distinction: mocks belong only in integration tests, never unit tests on pure domain logic; hand-written spies often beat a generic mock's verify(); assertions must check call count, not just occurrence; and you should only ever mock types your own codebase defines.
Spring and Database Testing
Testing Spring with SpringExtension
Loading a Spring ApplicationContext into a JUnit 5 test with @ExtendWith(SpringExtension.class) and @ContextConfiguration, injecting beans via @Autowired — with a note on today's @SpringJUnitConfig shortcut, from JUnit in Action, Third Edition, Ch. 16.
Testing Spring Boot Applications
Bootstrapping a full application context with @SpringBootTest — component scanning, auto-configuration, and bean autowiring — plus today's faster test slices (@WebMvcTest, @DataJpaTest) and the @MockBean→@MockitoBean change, from JUnit in Action, Third Edition, Ch. 17.
Testing a REST API with MockMvc
Driving a Spring REST controller through MockMvc — simulated HTTP requests, status/content-type/jsonPath assertions, and a mocked data layer — with today's @WebMvcTest slice, @MockitoBean (replacing @MockBean), and the Spring 6 NestedServletException change, from JUnit in Action, Third Edition, Ch. 18.
Testing JDBC and Spring JDBC
Testing data-access code against an in-memory database — from raw JDBC (Connection/PreparedStatement/ResultSet) to Spring's JdbcTemplate/NamedParameterJdbcTemplate — with notes on obsolete Class.forName driver loading, injected JdbcTemplate over JdbcDaoSupport, and the @JdbcTest slice, from JUnit in Action, Third Edition, Ch. 19.1–19.3.
Testing JPA and Hibernate
Testing a JPA/Hibernate persistence layer — entity mapping, persistence.xml, and EntityManager queries against a test database — with the critical javax→jakarta package move (Hibernate 6 / Spring 6), the @DataJpaTest slice, and Testcontainers for real-database integration tests, from JUnit in Action, Third Edition, Ch. 19.4–19.6.
Database Testing Prerequisites and Transaction Management
Why schema and reference data belong in source control, why migration-based delivery beats state-based once real data exists (the data-motion argument), and why integration tests need a separate transaction per arrange/act/assert section instead of reusing one session.
Test Data Lifecycle and What to Test at the Database Layer
Why cleaning up test data at the start beats the end, why in-memory databases still don't belong in integration tests despite Testcontainers changing the container-overhead trade-off, and why reads and repositories deserve a lower testing bar than writes.