Summary
We opened this chapter by introducing the foundations of software testing: what it is, why it matters, and how to approach it. We defined essential concepts such as SUT, the Arrange-Act-Assert structure, and code coverage, and explored the role of test doubles such as stubs, spies, and mocks.
From there, we covered TDD and BDD, before placing testing in the context of modern workflows such as CI and continuous delivery. We then explored 11 principles for effective testing, offering a mental framework to guide us through real-world challenges such as flaky tests, test interdependence, and the limitations of coverage metrics.
With the theory in place, we moved into practice: starting with unit tests in Node.js using the built-in node:test
runner. We tested both sync and async code and then focused on mocking, demonstrating how to isolate units from their dependencies, including HTTP calls, built-in modules, and custom internal modules. We then introduced DI as a cleaner...