A minimal Typescript setup that demonstrates unit testing in NodeJS and generating coverage. Since there are so many moving parts in the test ecosystem, this is an attempt to isolate them separately and debug them individually
yarn test- tests with coverage by c8 - coverage reporter inc8-coveragefolderyarn test:nyc- tests wth coverage by nyc - numbers not accurate, attempted fix -yarn test:nyc:fixdoes not work as wellyarn test:watch- does not work yet - limitation of mocha + ESMyarn test-only- run tests without coverage
TODO: Diagram to explain how all this fits in together comes here
- Specifying
moduleis the most important - this governs how the output transpiled file will look like - target is useful for any
feature replacementif need be - sourceMap is useful for debbugers
- Build time compilation is the invocation of the typescript compiler through an npm script like
yarn compile. The compiled files are written to thedistdirectory (or configuredoutDirintsconfig.json) - on-the-fly compilation is achieved by
require hooks-ts-nodeintercepts everyimportrequest and compiles theimportedmodule. It internally uses the sametsconfig.jsonfile but does not write todist- instead it uses a temporary directory and cleans it up soon after the module is loaded into memory
- Tests are driven by
mocha mochaallows for specifying hooks that it passes on to nodejs - we specify thets-node/esmhook to facilitate on-the-fly compilation of imports in test files- See
.mocharc.cjsfor full configuration used
- Coverage is usually one layer above tests
- You can generate coverage in 2 ways -
- Instrument your source and run your tests on the instrumented source as two separate steps
- On the fly instrumentation by specifying a nodejs require hook (sounds familiar, doesn't it ?)
- Coverage is generated by
nyc- for typescript, extend from the@istanbuljs/nyc-config-typescript - [Not working] Coverage numbers are wrong - the
@istanbuljs/esm-loader-hookneeds to be used, but I could not figure out a way to make it work. See issue - Coverage can also be generated via c8 which used nodejs' native coverage support - this is far simpler
to configure - see
.c8rc.json
- Add something like -
"instrument": "nyc instrument src in-src",to your package.json to instrument - Modify your imports under the
testdirectory to import fromin-srcwherever you import fromsrc - Remove on-the-fly instrumentation (can run tests directly, instead of through nyc - i.e
yarn test-only)
- Write source and tests in typescript
- Generate accurate coverage information with c8
- Watch tests (not working) - ESM watching is not supported in mochajs yet
- Coverage information generated with nyc not very accurate
A sampling of errors encountered - goes to show the many pieces of the puzzle here -
- TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "file:///add_test.ts"
This happens if you don't teach mocha about how to load ESM modules - can be fixed by the
"loader": "ts-node/esm"in .mocharc.cjs - ReferenceError: exports is not defined in ES module scope - this happens when typescript transpiles your code to commonjs format and includes an exports object - since this code is subsequently imported by a test through an ESM loader, it breaks
- Module is not defined in ES module scope - fix is to rename nyc.config.js to nyc.config.cjs
- We need to hook in the
@istanbuljs/esm-loader-hookfor nyc to generate the right coverage stats, but that fails with [ERR_UNKNOWN_FILE_EXTENSION]. It looks like setting this option for nyc disables the"experimental-specifier-resolution": "node"that we set for mocha (in.mocharc.cjs). Runtest:nyc:fixto reproduce this