From Breakpoint to Unit Test
2025-07-07, Mon
The other day I was required to write some JavaScript code in text editor instead of IDE. So I tried to complete the task without setting any breakpoint: instead, all the functions I wrote were tested continuously. At first I was not sure whether the work could be done in time, but out of my surprise, not only it was completed ahead of time, I also have much more confidence with the code I delivered, knowing that any change I made could be verified without much hassle.
Let's have some quick code to show how it played out: take Mocha1 as example, we have:
Step 1: Prepare the Environment2
mkdir -p ~/Workspace/SampleJS cd $_ # during the init process, set type to module and test command to mocha npm init # skip this step if mocha has been installed globally already npm install --save-dev mocha
Step 2: declare the interface that is going to be delivered, without implementation:
// ref: src/app.js export function greetings(name) { }
Step 3: define the test cases
// ref: test/app.test.js import assert from 'node:assert'; import { greetings } from '../src/app.js'; describe('App', function() { describe('greetings', function() { it('default', function() { assert.equal(greetings(), 'Hello, John Doe'); }); it('with name', function() { assert.equal(greetings('Jane'), 'Hello, Jane'); }); }); });
Step 4: Run the test case
npm test
All test cases should fail at this point, since no implementation has been provided yet.
With things set up properly, the development cycle boils down to
- Code: Update code according to requirement or failed test(s)
- Test: Update test cases (if necessary) and run them to report error
- Repeat: Repeat steps above until the requirement is fulfilled
Initially I thought this process couldn't beat "debugging with breakpoint" in terms of development efficiency. I'm glad I was wrong: not only this process provides a better view of the overall project design, it also tracks the evolution of development naturally through accumulating test cases, which is very much welcome.
In the case of something goes wrong, it is usually reflected in failed tests, whose granularity could be controlled freely. With properly defined scope, the place where things might have gone wrong could be located fairly easily. In most cases, that's exactly what we wanted, and this approach is way more efficient than debugging line by line, not to mention that test cases could be version controlled as well.
It has long been my habit to set up some breakpoints and start
debugging when things do not work as expected. For server side
JavaScript, the typically way would be either use some IDE (i.e. VS
Code, IntelliJ, etc.), or open chrome://inspect
in Chrome after the
Node inspector is enabled3:
node --inspect-brk src/app.js
This approach still works fine, but perhaps it's time to put it to the second chair.
As for unit test frameworks:
More testing frameworks for other languages could be found in the Wiki entry11.
Footnotes:
Mocha test framework https://mochajs.org
Code sample here uses ECMAScript Module by setting
type
to module
in package.json
, with export
and
import
. Without this config, it's the default CommonJS module that
that will be used by Node.js, with exports
and require
.
Node.js Debugging https://nodejs.org/en/learn/getting-started/debugging
Jest testing framework https://jestjs.io
Jasmine https://jasmine.github.io
Vitest https://vitest.dev
Unity: unit testing for C https://www.throwtheswitch.org/unity
Check: unit testing framework for C https://libcheck.github.io/check/
µnit, i.e. munit https://nemequ.github.io/munit/
List of unit testing frameworks https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks