You wrote a function. You tested it manually with console.log. It seems to work. You push to production. Two days later: "It fails when the input is negative." We see this every day in projects coming to us. Untested code is a promise of bugs. For Italian SMEs already working on thin margins, a production bug means lost customers and hours of debugging.
We at Meteora Web test everything we ship – Laravel backends, REST APIs, Vue components, business logic. We have been using Jest for years, on both JavaScript and TypeScript. In this guide, we show you how to set up a testing environment that is not a burden but a time-saver.
Why Jest and not another framework
Jest was born at Facebook (now Meta) to test React, but today it is the most widespread testing framework for JavaScript and TypeScript. It works out of the box: zero configuration for plain JS projects, a few lines for TypeScript. Compared to Mocha or Jasmine, it offers native mocking, coverage, and assertions – no need to install Chai, Sinon, Istanbul separately. Vitest is faster in some scenarios, but Jest has a more mature community, plugins for every need, and documentation covering every real case.
Sponsored Protocol
When to choose Jest
If you work with Node.js, React, Vue, Angular, or any stack producing JavaScript or TypeScript, Jest is the safest choice. If you need E2E testing, go with Playwright or Cypress; for unit and integration, Jest is perfect.
What you can do now: decide if your project is pure JS or TypeScript, then move to the next section.
Setting up Jest for JavaScript and TypeScript
Basic installation
npm install --save-dev jest
Add a script in package.json:
"scripts": {
"test": "jest"
}
Jest looks for files in __tests__/ or files with .test.js extension. Write your first test, e.g., sum.test.js:
const sum = (a, b) => a + b;
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
Run npm test and see the green. It already works.
Setting up for TypeScript
You have two paths: ts-jest (compiles TS on the fly) or @swc/jest (faster but fewer features). We use ts-jest for full compatibility with advanced types:
npm install --save-dev jest typescript ts-jest @types/jest
npx ts-jest config:init
This generates a jest.config.js ready for TypeScript. Add a test in .test.ts:
import { sum } from './sum';
test('typed sum', () => {
expect(sum(1, 2)).toBe(3);
});
If using @swc/jest, install and create a .jestrc:
Sponsored Protocol
{
"transform": {
"^.+\\.(t|j)sx?$": ["@swc/jest"]
}
}
What you can do now: install Jest and write a test for a function you use in your project. Even just test('true is true', () => expect(true).toBe(true)) to verify the environment works.
Writing tests that are worth the time
Describe, it, test
Organize tests in logical blocks with describe:
describe('discount calculation function', () => {
it('applies 10% for orders above 100€', () => {
expect(calcDiscount(150)).toBe(135);
});
it('does not apply discount below 100€', () => {
expect(calcDiscount(50)).toBe(50);
});
});
it and test are synonyms. Use it for readability.
Core matchers
toBe(value)— strict equality (Object.is)toEqual(value)— deep equality for objects and arraystoContain(item)— array or stringtoBeNull(),toBeUndefined(),toBeDefined()toBeTruthy(),toBeFalsy()toThrow(error)— for functions that throw exceptionstoHaveBeenCalled()— for mocks (see next chapter)
Common mistake: using toBe with objects. toBe compares reference, not values. Always use toEqual for objects and arrays.
Sponsored Protocol
Mocking: isolate real code
Your code calls an external API, reads from a database, or writes to the filesystem. You don't want the test to depend on live services. Mocks replace those dependencies with controlled versions.
jest.fn()
Create a fake function you can control:
const mockFn = jest.fn();
mockFn();
mockFn('arg1');
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('arg1');
You can also specify a return value:
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);
jest.mock()
Replace an entire module:
jest.mock('axios');
import axios from 'axios';
axios.get.mockResolvedValue({ data: { id: 1 } });
Every exported function from axios becomes an automatic mock.
jest.spyOn()
Spy on an existing method without replacing it, then restore:
const spy = jest.spyOn(console, 'log');
myFunction();
expect(spy).toHaveBeenCalledWith('hello');
spy.mockRestore();
What you can do now: take a function in your project that calls an external API and write a test that mocks it. Verify the behavior without hitting the server.
Async tests
JavaScript is asynchronous. Jest handles Promise and async/await natively:
Sponsored Protocol
test('fetches user data', async () => {
const data = await fetchUser(1);
expect(data.name).toBe('Mario');
});
If you don't return the Promise, the test passes before the operation finishes. Always return or await.
To test rejection:
test('fails with error', async () => {
await expect(fetchUser(-1)).rejects.toThrow('Invalid ID');
});
What you can do now: transform a synchronous test that calls an async function – make sure you use await or return.
Code coverage – how much are you testing?
Jest includes Istanbul for coverage. Just add --coverage:
npx jest --coverage
Generates a coverage/ folder with HTML reports. Metrics: lines, branches, functions, statements. 100% coverage is not mandatory – better a few well-written tests than many that cover only easy paths. We aim for > 80% branch coverage for critical code (calculations, validations, business logic).
You can set minimum thresholds in jest.config.js:
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 85,
lines: 90,
},
},
};
CI/CD integration
Tests must fail the build before the bug reaches production. On GitHub Actions, a simple workflow:
Sponsored Protocol
name: Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test -- --coverage
If a test fails, the PR is not merged. Seriously: we see this as a hard rule. No deployment without green tests.
What you can do now: add a coverage badge to your repository's README and set up a GitHub Actions workflow.
In summary – what to do now
- Install Jest with
npm install --save-dev jestoryarn add --dev jest. - Configure TypeScript if you use TS:
npm install --save-dev ts-jest @types/jestand runnpx ts-jest config:init. - Write a test for the simplest function in your project: a date formatter, a discount calculation, a validation.
- Mock external dependencies to make tests fast and independent.
- Set a coverage threshold and block the build if below 80%.
Tests are not an extra. They are the safety net that lets you modify code without fear. We at Meteora Web do not ship a single line without tests. Start today: your future self (and your clients) will thank you.