Jest is a JavaScript testing framework that’s become the de facto standard for many modern web development projects. It’s known for its speed, simplicity, and comprehensive feature set.

Here’s a simple calculator function we’ll test:

// calculator.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a, b) {
  if (b === 0) {
    throw new Error("Division by zero is not allowed.");
  }
  return a / b;
}

module.exports = { add, subtract, multiply, divide };

First, let’s set up Jest in your project. If you don’t have a package.json file, run npm init -y or yarn init -y. Then, install Jest as a development dependency:

npm install --save-dev jest
# or
yarn add --dev jest

Now, you need to tell npm or yarn to run Jest when you execute a test command. Open your package.json file and add or modify the scripts section like this:

{
  "scripts": {
    "test": "jest"
  }
}

Jest automatically looks for test files in a __tests__ directory or files named *.test.js or *.spec.js. Let’s create a test file for our calculator. Create a file named calculator.test.js in the same directory as calculator.js (or in a __tests__ folder).

Inside calculator.test.js, we’ll write our first tests using Jest’s describe and it (or test) functions.

// calculator.test.js
const { add, subtract, multiply, divide } = require('./calculator');

describe('Calculator', () => {
  describe('add', () => {
    it('should return the sum of two positive numbers', () => {
      expect(add(2, 3)).toBe(5);
    });

    it('should return the sum of a positive and a negative number', () => {
      expect(add(5, -2)).toBe(3);
    });

    it('should return the sum of two negative numbers', () => {
      expect(add(-5, -3)).toBe(-8);
    });

    it('should return zero when adding zero to a number', () => {
      expect(add(10, 0)).toBe(10);
    });
  });

  describe('subtract', () => {
    it('should return the difference of two positive numbers', () => {
      expect(subtract(10, 4)).toBe(6);
    });

    it('should return a negative number when subtracting a larger number from a smaller one', () => {
      expect(subtract(3, 7)).toBe(-4);
    });

    it('should handle subtraction with negative numbers', () => {
      expect(subtract(-5, -2)).toBe(-3); // -5 - (-2) = -5 + 2 = -3
    });
  });

  describe('multiply', () => {
    it('should return the product of two positive numbers', () => {
      expect(multiply(4, 5)).toBe(20);
    });

    it('should return a negative product when multiplying a positive and a negative number', () => {
      expect(multiply(6, -3)).toBe(-18);
    });

    it('should return a positive product when multiplying two negative numbers', () => {
      expect(multiply(-7, -2)).toBe(14);
    });

    it('should return zero when multiplying by zero', () => {
      expect(multiply(10, 0)).toBe(0);
    });
  });

  describe('divide', () => {
    it('should return the quotient of two positive numbers', () => {
      expect(divide(10, 2)).toBe(5);
    });

    it('should return a negative quotient when dividing a positive by a negative', () => {
      expect(divide(15, -3)).toBe(-5);
    });

    it('should return a positive quotient when dividing two negative numbers', () => {
      expect(divide(-20, -4)).toBe(5);
    });

    it('should throw an error when dividing by zero', () => {
      expect(() => divide(5, 0)).toThrow("Division by zero is not allowed.");
    });
  });
});

To run your tests, execute the command you added to package.json:

npm test
# or
yarn test

You should see output indicating that all tests have passed.

Jest’s describe block groups related tests, making your test suite organized. The it (or test) function defines an individual test case, describing what behavior is being tested. The core of a test is the expect function, which takes the value you want to test and chains it with a "matcher" function (like .toBe(), .toThrow(), .toEqual(), etc.) to assert a specific condition.

The expect(value).matcher(expectedValue) pattern is fundamental. For divide by zero, we use expect(() => divide(5, 0)).toThrow("Division by zero is not allowed.");. Notice the function is wrapped in an arrow function () => .... This is crucial because toThrow needs to execute the function to catch the error, not just check if the function exists.

The most surprising true thing about Jest is that its default behavior is to run tests in parallel, which significantly speeds up large test suites. It achieves this by forking processes for each test file, isolating them from each other.

Here’s a glimpse of the calculator code and its tests working together:

// calculator.js
function add(a, b) {
  return a + b;
}
// ... other functions ...
module.exports = { add, subtract, multiply, divide };

// calculator.test.js
const { add, subtract, multiply, divide } = require('./calculator');

describe('Calculator', () => {
  describe('add', () => {
    it('should return the sum of two positive numbers', () => {
      expect(add(2, 3)).toBe(5); // This assertion passes
    });
    // ... other tests ...
  });
  // ... other describe blocks ...
});

When you run npm test, Jest finds calculator.test.js, executes the code, and for each it block, it runs the function, then checks if the expect assertion holds true. If all assertions pass, the test passes. If any assertion fails, the test fails, and Jest reports the discrepancy.

The expect(value).matcher(expectedValue) structure is powerful. Matchers go beyond simple equality. For example, expect(array1).toEqual(array2) checks if two arrays have the same elements in the same order, while expect(object1).toMatchObject(object2) checks if an object contains a subset of properties with matching values.

The one thing most people don’t realize is how easily Jest can be configured to handle different environments, like Node.js or browser-like DOM environments, using testEnvironment in its configuration. By default, it uses node, but setting testEnvironment: 'jsdom' in a jest.config.js file allows you to test frontend code that interacts with the DOM, without needing a full browser.

The next step is exploring more advanced Jest features like mocking, code coverage, and snapshot testing.

Want structured learning?

Take the full Jest course →