Skip to content

Unit Testing

Unit testing is a fundamental aspect of testing where individual components of a program are tested to verify that each part functions correctly on its own. This approach focuses on the smallest parts of an application, typically individual functions or methods, to ensure that they perform their intended tasks as designed.

In practice, unit tests should be:

  • Independent: Each test should not depend on the state left by previous tests.
  • Reproducible: Tests should provide the same results every time, regardless of the environment or previous runs.
  • Fast: Tests should execute quickly to not slow down development or CI/CD pipelines.

Basic Usage of it and test

Vitest provides two interchangeable keywords for defining tests: it and test. Both are functionally equivalent and can be used based on personal or team preference. They help describe test cases clearly.

typescript
import { it, expect, test } from "vitest";

it("should work", () => {
  expect(true).toBe(true);
});

test('works with "test" as well', () => {
  expect(false).not.toBe(true);
});

Each function takes a string describing the test case and a callback function containing the assertions that actually test the code.

Expecting Failures

Vitest offers a unique feature with it.fails which is used to define a test that is expected to fail. This is useful for documenting bugs or behaviors that are known to be incorrect but have yet to be fixed.

typescript
it.fails("should be able to expect a test to fail", () => {
  expect(false).toBe(true);
});

Testing Asynchronous Code

Vitest handles asynchronous operations gracefully. You can return a promise in your test, and Vitest will wait for the promise to resolve before considering the test complete. This is useful for testing API calls, retry/polly operations, or any other asynchronous functionality.

typescript
test("works when returning a promise", () => {
  return new Promise((done) => {
    setTimeout(() => {
      expect("This should fail.").not.toBe("Totally not the same.");
      done(null);
    }, 0);
  });
});

Conditional Test Execution

Vitest allows conditional execution of tests using test.runIf and test.skipIf. This feature enables you to run tests only under certain conditions, making your test suite more flexible and environment-specific.

  • test.runIf runs the test only if the condition inside the parenthesis returns true. This is helpful for running tests that are relevant only in certain environments or configurations.
typescript
// npx vitest --mode=development --run --reporter=verbose
test.runIf(process.env.NODE_ENV === "development")(
  "it should run in development",
  () => {
    expect(process.env.NODE_ENV).toBe("development");
  }
);
  • test.skipIf skips the test if the condition inside the parenthesis returns true. It's useful for avoiding tests in environments where they're not applicable.
typescript
// npx vitest --run --reporter=verbose
test.skipIf(process.env.NODE_ENV !== "test")("it should run in test", () => {
  expect(process.env.NODE_ENV).toBe("test");
});

Assuming we have a math module math.ts:

typescript
//math.ts
export const add = (a: number, b: number): number => a + b;

export const subtract = (minuend: number, subtrahend: number): number =>
  add(minuend, -subtrahend);

export const multiply = (multiplicand: number, multiplier: number): number => {
  let result = 0;

  while (multiplier--) {
    result = add(result, multiplicand);
  }

  return result;
};

export const divide = (dividend: number, denominator: number): number => {
  return dividend / denominator;
};

export const sum = (...numbers: number[]): number => {
  return numbers.reduce((total, n) => total + n, 0);
};

export const average = (...numbers: number[]): number => {
  return divide(sum(...numbers), numbers.length);
};

The example below specifically tests the math module that includes functions for basic arithmetic operations like addition, subtraction, multiplication, and division.

Each arithmetic function has its dedicated describe block containing two test cases:

  • A positive test case to verify that the function returns the correct result under expected conditions.
  • A negative test case to ensure the function does not return incorrect results, helping to verify that the function handles edge cases or potential errors properly.

We can import the math.ts module and write tests for it. Create a math.spec.ts file in your src/test folder and add the following code

typescript
import { describe, it, expect } from "vitest";
import { add, subtract, multiply, divide } from "./math";

describe("add", () => {
  it("should add two numbers correctly", () => {
    expect(add(2, 2)).toBe(4);
  });

  it("should not add two numbers incorrectly", () => {
    expect(add(2, 2)).not.toBe(5);
  });
});

describe("subtract", () => {
  it("should subtract the subtrahend from the minuend", () => {
    expect(subtract(4, 2)).toBe(2);
  });

  it("should not subtract two numbers incorrectly", () => {
    expect(subtract(4, 2)).not.toBe(1);
  });
});

describe("multiply", () => {
  it("should multiply the multiplicand by the multiplier", () => {
    expect(multiply(3, 4)).toBe(12);
  });

  it("should not multiply two numbers incorrectly", () => {
    expect(multiply(4, 2)).not.toBe(1000);
  });
});

describe("divide", () => {
  it("should multiply the multiplicand by the multiplier", () => {
    expect(divide(12, 4)).toBe(3);
  });

  it("should not multiply two numbers incorrectly", () => {
    expect(multiply(4, 2)).not.toBe(1000);
  });
});

The describe function is used to group together similar tests in a single block. This helps organize tests into units that describe a specific part of an application or a feature. The name provided to the describe block usually corresponds to the function or feature being tested.

The it function is where the actual tests are defined. Each it block should represent a single, specific test case. The string description provided to it explains what the test should do, which ideally reflects a particular requirement or expectation of the application.