Jest Notes: Execution, Hooks, Focused Runs, Async, Coverage, and Test Doubles
Source: https://www.udemy.com/course/unit-testing-typescript-nodejs/
Good tests are often summarized by FIRST:
- Fast: quick feedback so you actually run them often.
- Independent: tests don’t rely on each other’s order or shared state.
- Repeatable: same result every run (avoid time/network randomness unless controlled).
- Self-validating: clear pass/fail without manual inspection.
- Thorough: covers meaningful paths (happy path + edge cases + failure paths).
A simple principle to keep in mind: when a test finishes, it should either pass or fail.
How Jest executes tests: parallel files, sequential tests
Jest has two important execution behaviors:
- File-level parallelism: different test files run in parallel across multiple worker processes.
- In-file sequentiality: tests inside a single file run sequentially by default.
Run everything sequentially (useful for debugging)
If you’re debugging or dealing with shared resources that can’t be accessed concurrently, run Jest in a single process:
jest --runInBandJest hooks (setup & teardown)
Jest provides lifecycle hooks to prepare and clean up state:
beforeEach: runs before every test in the current scopeafterEach: runs after every testbeforeAll: runs once before all tests in the current scopeafterAll: runs once after all tests
Typical usage
- Unit tests:
beforeEach/afterEachare common to reset state per test. - Integration tests:
beforeAll/afterAllare useful for expensive setup/teardown (DB connection, server start/stop).
Running only some tests: .only, .skip, todo, and concurrent
“Run only me”
These are equivalent in intent:
it.only,test.only,describe.only- aliases:
fit,fdescribe
“Skip me”
it.skip,test.skip,describe.skip- aliases:
xit(≈it.skip),xtest(≈test.skip)
TODO tests
A nice way to park tests you know you need, without implementing them yet:
test.todo('should handle invalid input');Concurrent tests
Jest also supports concurrency within a file:
test.concurrent('...', async () => { ... });This runs concurrently with other test.concurrent tests. Use it only when tests are truly isolated (no shared DB/files/global mocks).
Important nuance: .only does NOT stop other files from running
it.only / describe.only only affects tests inside that file.
Jest still runs other test files because:
- it first selects test files based on config (
testMatch,testRegex, etc.), - and only after a file loads does it discover which tests are marked
.only.
Practical takeaway: if you focus a test in one file, other test files can still execute normally unless you apply additional filtering (watch filters, CLI patterns, etc.).
Testing errors: toThrow
To assert an error is thrown, pass a function to expect:
const expectError = () => {
throw new Error('boom');
};
expect(expectError).toThrow();
expect(expectError).toThrow('boom'); // optional message matchUseful matchers
A few handy Jest matchers:
expect(x).toBeInstanceOf(MyClass);
expect(obj).toHaveProperty('key', value);
expect(obj).toHaveProperty('nested.key', value); // supports nested pathsAsync tests: done and modern alternatives
Using done (callback-style async)
Jest supports done for callback-based APIs:
- call
done()→ pass - call
done(err)ordone('message')→ fail
test('callback async', (done) => {
someAsyncFn((err, result) => {
if (err) return done(err);
expect(result).toBe(42);
done();
});
});Prefer Promises / async-await (most modern code)
Whenever possible, prefer returning a Promise or using async/await.
About fail(...) (why it’s unreliable)
Some test environments expose a global fail('message'), but it isn’t guaranteed across all Jest setups, so relying on it can be inconsistent.
Reliable alternatives:
throw new Error('should not reach here');or in async flows:
return somePromise().then(() => {
throw new Error('expected promise to reject');
});Watch mode and running tests
Many projects use watch mode:
{
"scripts": {
"test": "jest --watch"
}
}In watch mode, Jest reruns tests when files change and offers interactive filtering. Some people prefer manual runs for predictable, repeatable execution—especially when debugging.
Debugging Jest tests in VS Code (high level)
A common workflow:
- Open Run and Debug in VS Code
- Create a Node.js
launch.jsonconfiguration for Jest - Add breakpoints in your test
- Run the debug configuration
Coverage: collectCoverage and Istanbul
Jest can generate code coverage reports:
collectCoverage: true,
collectCoverageFrom: ['<rootDir>/src/app/**/*.ts'],This produces a coverage/ folder with reports. Under the hood, Jest uses Istanbul for coverage instrumentation.
If you need to exclude lines/files, look up Istanbul ignore directives (e.g., /* istanbul ignore next */).
Test doubles: dummy, fake, stub, spy, mock
A test double is a stand-in for a real dependency.
Common types:
- Dummy: passed around but never used
- Fake: simplified working implementation (takes shortcuts)
- Stub: returns canned values, often minimal behavior
- Spy: records how something was called (args, call count)
- Mock: like a spy but also preprogrammed with expectations/behavior
In Jest, you’ll mostly use:
jest.fn()(mock functions)jest.spyOn(obj, 'method')(spies on real methods)jest.mock('module')(mock modules)
Testing styles (London vs Chicago)
- London style: interaction-based testing (more mocks; verify calls)
- Chicago style: state-based testing (fewer mocks; verify outcomes/state)