1. Introduction
The diagram below comes from Learning Curves (for different programming languages). Although the article is primarily humorous, it shows the author’s attitude towards unit testing.

For Python programmers, as experience grows, after mastering unit testing, personal productivity gets a sudden boost (as for mastering decorators, one might feel inflated, but the efficiency improvement is not obvious).
Many years ago, just out of school, in the C++ era. A Java expert from former Nortel evangelized to the development team, recommending cpp-unit, saying unit testing “can significantly improve individual combat capability.” Time passes, through many battles, I’ve lost contact with Eddie from Nortel, but his evangelism was clearly successful. I’ve practiced unit testing in different projects:
- cpp unit
- junit
- luaunit
- python unittest
- jest
Although the languages are different, the unit testing concept is the same, with concepts like setup, teardown, testcase, assert/expect, and mock.
I “cannot agree more” with the statement that unit testing “improves individual combat capability”.
2. Benefits of Unit Testing
The benefits of unittest are mainly in two aspects.
2.1. Encourages Designing Interfaces First
To do test-driven development, you have to think about how to test first, which promotes considering issues from the perspective of interface definition at an early stage. It also promotes low coupling and high cohesion in modules. Additionally, it brings an extra benefit: TDD also deposits test cases similar to documentation. Years later, when reviewing software, looking at the test cases basically explains the thinking at that time.
2.2. Low Cost to Run Tests
With standardized test case writing and test framework support for convenient execution and feedback, running tests has no time or effort burden, and can be run anytime. Running tests frequently and continuous integration exposes problems early. After interfaces stabilize, having test cases for quality assurance makes refactoring convenient.
3. Example
Suppose you’re developing an API server. Unit testing at different levels of granularity can be:
- From the API level, using HTTP client to simulate requests, then assert whether the returned results meet expectations
- From the Handler level, simulate HTTP request headers, params, body, etc., then feed to the Handler, see if the returned structure meets expectations
- At the module level, if there are other business logic modules below the Handler, you can do unit testing against the module interface
The above 1 can already be considered system testing (end-to-end testing).
2 has some additional benefits compared to 1: In general, for handling a request, besides the Handler, there are some middleware for preprocessing. For 1, you must test the middleware functions and the Handler itself as a whole. It’s not flexible enough.
2 also has some disadvantages compared to 1: It requires mocking. Depending on the framework used, you need to mock the input and output data structures. Fortunately, mocking is generally simple, and many frameworks have third-party mock libraries. For example, below is a test for JS express Handler:
import { posthandler } from '../src/handlers/post';
import { getMockReq, getMockRes } from '@jest-mock/express';
// generate a mocked response and next function, with provided values
const { res, next } = getMockRes({
})
test('check post handler returns token in JSON body', async () => {
// generate a mock request with params
const req = getMockReq({ params: { id: 'abc-def' }, headers:{authorization:'this is my token'} })
// provide the mock req, res, and next to assert
await posthandler(req, res)
expect(res.json).toHaveBeenCalledWith(
expect.objectContaining({
authorization: 'this is my token',
}),
)
})
The introduced third-party jest-mock/express helps generate input data and check output data. See? Easy.
4. Conclusion
For most projects of any scale, as long as you introduce a few simple concepts, developers can get started in an hour or two. Organizing asserts for different modules into test cases, making it convenient to run test cases, is enough. Small investment, high return. Essential for junior programmers to advance.