1.1 The first step · 1.2 Defining unit testing, step by step · 1.3 Entry points and exit points · 1.4 Exit point types · 1.5 Different exit points, different techniques · 1.6 A test from scratch · 1.7 Characteristics of a good unit test · 1.7.1 What is a good unit test? · 1.7.2 A unit test checklist · 1.8 Integration tests · 1.9 Finalizing our definition · 1.10 Test-driven development · 1.10.1 TDD: Not a substitute for good unit tests · 1.10.2 Three core skills needed for successful TDD · 1.11 Summary
2.1 Introducing Jest · 2.1.1 Preparing our environment · 2.1.2 Preparing our working folder · 2.1.3 Installing Jest · 2.1.4 Creating a test file · 2.1.5 Executing Jest · 2.2 The library, the assert, the runner, and the reporter · 2.3 What unit testing frameworks offer · 2.3.1 The xUnit frameworks · 2.3.2 xUnit, TAP, and Jest structures · 2.4 Introducing the Password Verifier project · 2.5 The first Jest test for verifyPassword · 2.5.1 The Arrange-Act-Assert pattern · 2.5.2 Testing the test · 2.5.3 USE naming · 2.5.4 String comparisons and maintainability · 2.5.5 Using describe() · 2.5.6 Structure implying context · 2.5.7 The it() function · 2.5.8 Two Jest flavors · 2.5.9 Refactoring the production code · 2.6 Trying the beforeEach() route · 2.6.1 beforeEach() and scroll fatigue · 2.7 Trying the factory method route · 2.7.1 Replacing beforeEach() completely with factory methods · 2.8 Going full circle to test() · 2.9 Refactoring to parameterized tests · 2.10 Checking for expected thrown errors · 2.11 Setting test categories · 2.12 Summary
3.1 Types of dependencies · 3.2 Reasons to use stubs · 3.3 Generally accepted design approaches to stubbing · 3.3.1 Stubbing out time with parameter injection · 3.3.2 Dependencies, injections, and control · 3.4 Functional injection techniques · 3.4.1 Injecting a function · 3.4.2 Dependency injection via partial application · 3.5 Modular injection techniques · 3.6 Moving toward objects with constructor functions · 3.7 Object-oriented injection techniques · 3.7.1 Constructor injection · 3.7.2 Injecting an object instead of a function · 3.7.3 Extracting a common interface · 3.8 Summary
4.1 Interaction testing, mocks, and stubs · 4.2 Depending on a logger · 4.3 Standard style: Introduce parameter refactoring · 4.4 The importance of differentiating between mocks and stubs · 4.5 Modular-style mocks · 4.5.1 Example of production code · 4.5.2 Refactoring the production code in a modular injection style · 4.5.3 A test example with modular-style injection · 4.6 Mocks in a functional style · 4.6.1 Working with a currying style · 4.6.2 Working with higher-order functions and not currying · 4.7 Mocks in an object-oriented style · 4.7.1 Refactoring production code for injection · 4.7.2 Refactoring production code with interface injection · 4.8 Dealing with complicated interfaces · 4.8.1 Example of a complicated interface · 4.8.2 Writing tests with complicated interfaces · 4.8.3 Downsides of using complicated interfaces directly · 4.8.4 The interface segregation principle · 4.9 Partial mocks · 4.9.1 A functional example of a partial mock · 4.9.2 An object-oriented partial mock example · 4.10 Summary
5.1 Defining isolation frameworks · 5.1.1 Choosing a flavor: Loose vs. typed · 5.2 Faking modules dynamically · 5.2.1 Some things to notice about Jest’s API · 5.2.2 Consider abstracting away direct dependencies · 5.3 Functional dynamic mocks and stubs · 5.4 Object-oriented dynamic mocks and stubs · 5.4.1 Using a loosely typed framework · 5.4.2 Switching to a type-friendly framework · 5.5 Stubbing behavior dynamically · 5.5.1 An object-oriented example with a mock and a stub · 5.5.2 Stubs and mocks with substitute.js · 5.6 Advantages and traps of isolation frameworks · 5.6.1 You don’t need mock objects most of the time · 5.6.2 Unreadable test code · 5.6.3 Verifying the wrong things · 5.6.4 Having more than one mock per test · 5.6.5 Overspecifying the tests · 5.7 Summary
6.1 Dealing with async data fetching · 6.1.1 An initial attempt with an integration test · 6.1.2 Waiting for the act · 6.1.3 Integration testing of async/await · 6.1.4 Challenges with integration tests · 6.2 Making our code unit-test friendly · 6.2.1 Extracting an entry point · 6.2.2 The Extract Adapter pattern · 6.3 Dealing with timers · 6.3.1 Stubbing timers out with monkey-patching · 6.3.2 Faking setTimeout with Jest · 6.4 Dealing with common events · 6.4.1 Dealing with event emitters · 6.4.2 Dealing with click events · 6.5 Bringing in the DOM testing library · 6.6 Summary
7.1 How to know you trust a test · 7.2 Why tests fail · 7.2.1 A real bug has been uncovered in the production code · 7.2.2 A buggy test gives a false failure · 7.2.3 The test is out of date due to a change in functionality · 7.2.4 The test conflicts with another test · 7.2.5 The test is flaky · 7.3 Avoiding logic in unit tests · 7.3.1 Logic in asserts: Creating dynamic expected values · 7.3.2 Other forms of logic · 7.3.3 Even more logic · 7.4 Smelling a false sense of trust in passing tests · 7.4.1 Tests that don’t assert anything · 7.4.2 Not understanding the tests · 7.4.3 Mixing unit tests and flaky integration tests · 7.4.4 Testing multiple exit points · 7.4.5 Tests that keep changing · 7.5 Dealing with flaky tests · 7.5.1 What can you do once you’ve found a flaky test? · 7.5.2 Preventing flakiness in higher-level tests · 7.6 Summary
8.1 Changes forced by failing tests · 8.1.1 The test is not relevant or conflicts with another test · 8.1.2 Changes in the production code’s API · 8.1.3 Changes in other tests · 8.2 Refactoring to increase maintainability · 8.2.1 Avoid testing private or protected methods · 8.2.2 Keep tests DRY · 8.2.3 Avoid setup methods · 8.2.4 Use parameterized tests to remove duplication · 8.3 Avoid overspecification · 8.3.1 Internal behavior overspecification with mocks · 8.3.2 Exact outputs and ordering overspecification · 8.4 Summary
10.1 Common test types and levels · 10.1.1 Criteria for judging a test · 10.1.2 Unit tests and component tests · 10.1.3 Integration tests · 10.1.4 API tests · 10.1.5 E2E/UI isolated tests · 10.1.6 E2E/UI system tests · 10.2 Test-level antipatterns · 10.2.1 The end-to-end-only antipattern · 10.2.2 The low-level-only test antipattern · 10.2.3 Disconnected low-level and high-level tests · 10.3 Test recipes as a strategy · 10.3.1 How to write a test recipe · 10.3.2 When do I write and use a test recipe? · 10.3.3 Rules for a test recipe · 10.4 Managing delivery pipelines · 10.4.1 Delivery vs. discovery pipelines · 10.4.2 Test layer parallelization · 10.5 Summary
11.1 Steps to becoming an agent of change · 11.1.1 Be prepared for the tough questions · 11.1.2 Convince insiders: Champions and blockers · 11.1.3 Identify possible starting points · 11.2 Ways to succeed · 11.2.1 Guerrilla implementation (bottom-up) · 11.2.2 Convincing management (top-down) · 11.2.3 Experiments as door openers · 11.2.4 Get an outside champion · 11.2.5 Make progress visible · 11.2.6 Aim for specific goals, metrics, and KPIs · 11.2.7 Realize that there will be hurdles · 11.3 Ways to fail · 11.3.1 Lack of a driving force · 11.3.2 Lack of political support · 11.3.3 Ad hoc implementations and first impressions · 11.3.4 Lack of team support · 11.4 Influence factors · 11.5 Tough questions and answers · 11.5.1 How much time will unit testing add to the current process? · 11.5.2 Will my QA job be at risk because of unit testing? · 11.5.3 Is there proof that unit testing helps? · 11.5.4 Why is the QA department still finding bugs? · 11.5.5 We have lots of code without tests: Where do we start? · 11.5.6 What if we develop a combination of software and hardware? · 11.5.7 How can we know we don’t have bugs in our tests? · 11.5.8 Why do I need tests if my debugger shows that my code works? · 11.5.9 What about TDD? · 11.6 Summary
12.1 Where do you start adding tests? · 12.2 Choosing a selection strategy · 12.2.1 Pros and cons of the easy-first strategy · 12.2.2 Pros and cons of the hard-first strategy · 12.3 Writing integration tests before refactoring · 12.3.1 Read Michael Feathers’ book on legacy code · 12.3.2 Use CodeScene to investigate your production code · 12.4 Summary
mockingA programmable test double that replaces an outgoing dependency and records or verifies interactions with it.
stubA stub is a test double that replaces an incoming dependency by providing controlled behavior or data without verifying interactions with it.
integration testTesting that verifies behavior across multiple integrated components or against external systems such as databases and networks.
dependencyAn imported or required software component on which a module relies.
exit pointAn observable result produced when a unit of work finishes, such as a returned value, state change, or interaction with an uncontrolled external system.
trustConfidence that tests reliably reveal whether the covered functionality is working correctly.
flaky testTest flakiness is the tendency of tests to produce inconsistent pass-or-fail results without a relevant code change.
maintainabilityThe degree to which software or tests can be understood, modified, corrected, or evolved efficiently over time.
entry pointAn externally triggerable function or interface through which execution of a unit of work begins.
code readabilityThe quality of test code that determines how easily it can be read and understood.
test-driven developmentA development technique in which a failing test is written before the production code that makes it pass, followed by refactoring or another test.
dependency injectionA technique in which a component receives a value or behavior from outside rather than creating it internally.
test doubleA test double is a replacement for a real dependency that provides simplified behavior or enables observation during a test.
JestJest is a JavaScript testing framework that provides facilities for writing, running, and reporting tests.
factoryA factory function is a function that creates and returns an object, value, or configured function.
mocking frameworkA software framework that creates and configures test doubles and supports checking their interactions.
assertionAn assertion is a test condition that verifies whether an actual result or behavior satisfies an expected truth condition.
real dependencyA dependency whose actual implementation or external state is used during a test rather than a controlled substitute.
regressionA previously working software behavior that becomes broken after a change.
refactoringRefactoring is changing the internal structure of code without changing its externally observable functionality.
functional, stateless philosophyA programming paradigm that emphasizes function composition, expressions, and minimizing mutable state and side effects.
end-to-end testingA system end-to-end test verifies behavior through the complete running system and its operational boundaries.
test automation frameworkA software framework that provides infrastructure and APIs for defining, running, and reporting tests.
false positiveA false positive, or false test failure, is a reported test failure even though the underlying production behavior is correct.
antipatternAn antipattern is a commonly used approach that appears reasonable but produces undesirable consequences or problems.
test nameThe textual name assigned to a test to communicate the tested unit, scenario, and expected behavior or exit point.
high-level testA higher-level test verifies behavior across relatively broad system boundaries and typically involves multiple components or external interactions.
scenarioA test scenario is a defined situation, set of inputs, or conditions under which software behavior is tested.
seamA seam is a location in code where behavior or dependencies can be replaced for testing.
return valueA return value is data produced by a function and delivered to its caller when the function completes.
test recipeA test recipe is a prescribed sequence of setup, execution, and verification steps for running a test scenario.
legacy codeOlder application source code that is difficult to change, test, or understand and may no longer be supported in its original environment.
low-level testA low-level test verifies a smaller or more isolated unit of software behavior than a higher-level test.
value-based testA test that verifies behavior by examining the value returned by the code under test.
system under testA system under test is the subject, system, suite, component, class, or code being tested.
assertion rouletteA testing anti-pattern in which multiple assertions are placed in one test so that an early failure prevents later assertions from being evaluated or reported.
adapterAn adapter is a component that abstracts or translates an underlying dependency behind a replaceable interface.
module injectionModule injection is a dependency-injection technique in which a module receives or imports replaceable collaborators through its module boundary.
incoming dependencyAn incoming dependency is a dependency that supplies data or behavior to a unit of work without representing its eventual externally observable result.
behaviorBehavior of software that can be detected through outputs, state changes, or other effects visible to an external observer.
Interface Segregation PrincipleThe interface segregation principle favors small, client-specific interfaces instead of forcing consumers to depend on broad interfaces.
extract adapter patternA testability pattern that isolates an inherently asynchronous operation behind an abstraction so it can be replaced by synchronous behavior during testing.
hexagonal architectureAn architectural pattern that isolates application logic behind ports and connects it to external systems through replaceable adapters.
parameterized testingA parameterized test is a test whose logic runs repeatedly with different values drawn from a parameter set or table.
outgoing dependencyA dependency through which a unit of work produces an external effect or reaches an exit point.
abstractionA simplified boundary that hides implementation details while exposing a controlled interface to underlying behavior or dependencies.
state-based testingA state-based test verifies behavior by examining the observable state produced or changed by the code under test.
interaction testingA testing technique that verifies how a unit of work interacts with its outgoing dependencies, including the calls made and their parameters.
object-oriented programmingA programming paradigm that structures software around objects encapsulating state and behavior.
duck typingDuck typing is a type-compatibility approach that accepts an object based on the operations it supports rather than its declared type or class.
test runnerSoftware that executes tests, reports their results, and proceeds between test cases according to its execution rules.
arrange act assert patternThe Arrange-Act-Assert pattern structures a test into setup of test conditions, execution of the behavior under test, and verification of the outcome.
async/awaitAsync/await is JavaScript syntax for expressing promise-based asynchronous control flow in a sequential-looking form.
jest.mockA Jest API that replaces a specified imported or required module with an automatically generated or configured mock.
fake loggerA lightweight logger test double that records supplied messages in accessible state instead of writing them to a real logging destination.
constrained test orderConstrained test order is an antipattern in which a test depends on another test having executed or not executed first.
test isolationTest isolation is the property that each test runs independently without relying on or altering the state or execution of other tests.
expectationAn expectation is the behavior or result that a test predicts for a specified unit and scenario.
parallel test executionParallel test execution is the concurrent execution of multiple tests to reduce total test execution time.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.