Check is a personal project for now. It is very raw and undocumented at this stage.
It's an exploration at writing a test framework that addresses some of jest
's shortcomings (imho):
- when multiple tests fail in a block, you only see the first one
- the automatic hoisting of imports and mocks
- this makes it difficult to write you own helpers on top of
jest
- this makes it difficult to write you own helpers on top of
- the need to use
beforeEach
/afterEach
(detailed explanation here)- which result in the need to use convoluted syntax when you need to access something inside the
beforeEach
- which result in the need to use convoluted syntax when you need to access something inside the
- missing matchers for React-based use-cases (probably other frameworks as well)
- when testing a component, it's difficult to test for only some props
- ...
install dependencies by running
yarn
create your .env
file by copying .env.sample
details here
To try it out, clone the repository, have a look at files in the src/example
folder: example.ts
& test files.
You can first run the test using jest
with yarn jest
→ 3 tests will fail, 1 because it's supposed to, and 2 because of how jest
execute the tests. ❌
You can then run the test using this new framework with yarn check
→ 1 test will fail (it's supposed to) and all other will pass. ✔
argument | default value | meaning |
---|---|---|
configuration file path | check.config.json |
cf. below |
--watch |
false | will watch for file changes to re-trigger tests |
parameter | default value | meaning |
---|---|---|
testFilesPattern |
**/*.test.ts |
the pattern to get the test files to run |
watchFilesPattern |
**/* |
(watch mode only) the pattern to get the files to watch |
watchFilesIgnored |
['node_modules', '.git', '.swc', 'dist', 'out'] |
(watch mode only) files or folder not to watch |
In watch mode, tests are re-run every time you save a file.
- If only one test file is run, you'll see this file full test tree (and individual status) in your terminal
- If more than one test file is run, you'll see only the global status for each test file
- In both cases, you'll see details for failing tests (see examples below)
You also have access to additional functionalities as described in your terminal:
Ran all tests.
press <f> to filter
press <ctrl> + <c> to exit
- pressing ctrl + c will stop the watch mode and exit
- pressing f will allow you to enter a filter on which tests to run. You'll see a live list of files affected by your filter.
- once you press enter, only those tests will be run (still on watch mode)
- you can later press f again to change that filter (entering nothing runs all tests)
- in test files
- accepts
describe
andit
/test
syntax - standard matcher are available:
toEqual
matcher (useslodash
)not
function
- accepts
spy
definition & exposes related spy matchers (cf. section below)
- accepts
- prints the result in the console (using
chalk
)- if only one test file, prints global result (PASS/FAIL), detailed tests tree & errors
- if multiple test files, prints global result (PASS/FAIL) & errors for each file (no detailed tests tree)
- in both cases, in case of errors,
check
continues to run tests and show all tests that are failing, not just the first one
you can defined a spy by specifying the module & declaration you want to spy:
const spyExistsSync = spy('fs', 'existsSync')
optionally, you can add typing to your spy function:
import { type existsSync } from 'fs';
const spyExistsSync = spy<typeof existsSync>('fs', 'existsSync');
Regular matchers don't work on spy function, instead, they have their dedicated set of spy matchers. So far:
toBeCalled
matcher (will compare the number in parameter with the spy function inner counter)- first argument is the number of calls to match
- second argument (optional) is an array of parameters to match (match, not equal)
- if both are provided, it will succeed if the function has been called X times with arguments matching those provided
A note about typings:
check
's types are smart enough so that depending on the parameter passed to theit
/test
function:
- if this parameter is a spy functions, only spy matchers are available (not standard matchers)
- if this parameter is not a spy function, only standard matchers are available (not spy matchers)
See dedicated doc for more advanced information about the spy
function.
multiple files | single file (PASS) | single file (FAIL) |
---|---|---|
check | jest |
---|---|
output tests in the written order | reorders tests (strict hierarchical order) |
all tests pass ✔ | one test fail ❌ |
- the whole thing is written in Typescript and runs using
ts-node
(with the currently experimental@swc
transpiler for performance) - it works on Typescript test files (usually
xxxx.test.ts
) - each file is handled in its own thread using node's
worker_thread
- first, the parser (cf. src/parser) will:
- read the file (it must be utf-8)
- transpile it (using Typescript) in CommonJS & the Latest configuration from ts
- get the transpiled file tree (using Typescript's
createSourceFile
) - parse it into JSON using the project's own parser (output in
out/****-result.json
ifWRITE_DEBUG_FILES
is set)
- then, the runner will get the parser result and:
- transform it into runs (output in
out/****-runs.json
ifWRITE_DEBUG_FILES
is set)- a hierarchical representation of the test suites
- with code & tests being grouped in an array at the test level
- this array contains the whole code & test you pass through to go to that test → they will be executed this way
- execute those runs
- using the node vm
- using a brand new context for each run to avoid side-effects
- output a result in the console (cf. images above)
- transform it into runs (output in
This diagram describes how tests are running without the watch mode.
TBD: describe how watch mode works internally
flowchart TD
A[get test files] --> B(("one worker_thread\n per file"))
B --> |test file 1| C1["read (utf-8)"]
B --> |test file 2| C2["read (utf-8)"]
subgraph parser
direction LR
subgraph file 1
C1 --> D1[transpile\nusing Typescript]
D1 --> E1["parse file tree\ninto a Line[]"]
end
subgraph file 2
C2 --> D2[transpile\nusing Typescript]
D2 --> E2["parse file tree\ninto a Line[]"]
end
end
subgraph runner
subgraph file 1
E1 --> F1["transform\ninto Run[]"]
F1 --> G1A["execute\nRun 1"]
F1 --> G1B["..."]
F1 --> G1C["execute\nRun n"]
G1A --> H1["aggregate\nRun[] results"]
G1B --> H1["aggregate\nRun[] results"]
G1C --> H1["aggregate\nRun[] results"]
end
subgraph file 2
E2 --> F2["transform\ninto Run[]"]
F2 --> G2A["execute\nRun 1"]
F2 --> G2B["..."]
F2 --> G2C["execute\nRun n"]
G2A --> H2["aggregate\nRun[] results"]
G2B --> H2["aggregate\nRun[] results"]
G2C --> H2["aggregate\nRun[] results"]
end
end
H1 --> W[aggregate results]
H2 --> W[aggregate results]
W --> X{success}
X --> |Yes| Y[Exit with status 0]
X --> |No| Z[Exit with status 1]
name | meaning |
---|---|
WRITE_DEBUG_FILES |
true → write debug files (result.json & runs.json ) in the out folder |
- output details when errors
- logical path of the error
- meaningful message, including comparison
- code line (
might be tricky because of transpilationwill work thx to the use of sourcemaps)
- test the framework using itself
- other matchers
- watch (including code files changes with
chokidar
)- watch & cancel running tests if necessary
- allow to configure watched files pattern & ignored files
- on watch mode, allow to input a custom Regex (and see result live)
- on watch mode, cache the runs extracted from the test files (based on file hash?)
- don't use
jest
types but our own- expect.toEqual should be generic in order to support type completion inside toEqual
- mock & spy
- spy
- mock
- deal with multiple files
- accept a glob / pattern
- run multiple files in parallel
- output short version of pass/fail when multiple files
- output recap of errors if any
- read config instead of cli arguments
- expose commands to be used from the outside:
check example
- write recap at the end (nb test suites, nb tests, time, etc.)
- publish a package
- deal with React specificities: props, etc.
- dev experience: can interactively set pattern, etc.
- only write debug json files when env var set