Execute test code with test() and it()

On this site, HowToTestFrontend.com, we use either Jest or Vitest as the test runner - the choice makes no difference to how you write tests, as the syntax we use is compatible with both.

Our test environment supports both Jest and Vitest APIs, so you can choose whichever one you prefer to use.

The tests themselves are written in JavaScript or TypeScript - you just assert how values should behave.

For example

expect(1 + 1).toBe(2);

expect([
  'apples',
  'bananas',
]).toHaveLength(2);

expect(() =>
  doSomethingThatWillThrow()
).toThrow(
  new Error('Some Thrown Error')
);

You put these assertions (and other code to set up whatever you are testing) inside a test() or it() block.

These functions take two arguments:

  • the first argument is the name of the test
  • the second argument is the test function itself

Example:

// `Calculator` class would be imported in, not shown in this demo code
test('can calculate some numbers', () => {
  const someCalculator =
    new Calculator();

  const actualResult =
    someCalculator.add(1, 1);
  const expectedResult = 2;

  expect(actualResult).toBe(
    expectedResult
  );
});

It vs Test

  • it() and test() are exactly the same function.
  • Most codebases have styling guidelines where everyone uses either it() or test().
  • If you choose it(), you often write titles that read as sentences, e.g. it should add two numbers would be it('should add two numbers').
  • You can enforce this via ESLint (e.g. the jest/valid-title rule with a mustMatch pattern) so that test titles with it() start with 'should'
// These two tests are functionally identical
test('should add two numbers correctly', () => {
  expect(1 + 2).toBe(3);
});

it('should add two numbers correctly', () => {
  expect(1 + 2).toBe(3);
});

Async test functions

The second argument (the function) can be an async function.

When we get to testing frontend applications this will be very typical (as it is required for many state changes and interactions).

For now in this lesson we'll stick to simpler examples though.

Tip: How test() and it() are typed in Typescript

This might give you a better understanding of how the second argument to it() or test() works:

test(name: string, fn: () => void | Promise<void>)
it(name: string, fn: () => void | Promise<void>)

A simple await in a test looks like this:

it('should fetch user data', async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe('Bob');
});

We will cover async behaviour in more detail in an upcoming lesson.

You can also pass in named functions as the second argument

Instead of writing inline anonymous functions, you can pass named functions as the second argument to test() and it().

Note: This isn't super common, but it can make organising your tests easier in some code-bases.

// All test declarations grouped together
it(
  'should add positive numbers correctly',
  testAddingPositiveNumbers
);

it(
  'should handle zero values',
  testHandlingZeroValues
);

it(
  'should multiply decimals accurately',
  testMultiplyingDecimals
);

it(
  'should throw error when dividing by zero',
  testDividingByZero
);

function testAddingPositiveNumbers() {
  expect(1 + 1).toBe(2);
}
// other functions not shown here

Lesson Task

In the test runner (use the tabs above), add a test with a name (1st argument) and a test function (2nd argument) that includes an expect() call that runs successfully.

Here is an example of something you can copy/paste and use as the test function (the second argument):

() => {
  expect(1 + 2).toBe(3);
};

Once you have written it in the test runner, click the run button and then continue to the next lesson.

Ready to try running tests for this lesson?