Writing Effective Unit Tests

Anatomy of a unit test

A unit test is a piece of code that checks if a small part of your application works correctly.

Let’s start with the simplest form of a test:

javascript
	import { expect, test } from "vitest";
	import { functionToTest } from "./yourModule";
	
	test("description of what the test is checking", () => {
	  // Arrange: Set up the test data
	  const input = "some input";
	  const expectedOutput = "expected output";
	
	  // Act: Call the function being tested
	  const result = functionToTest(input);
	
	  // Assert: Check if the result matches the expected output
	  expect(result).toBe(expectedOutput);
	});

Let’s break this down:

  1. Import statements: We bring in the tools we need from Vitest and the function we want to test.
  2. Test function: We use test() to create a new test. We could also use it() instead of test() as they aliases. We give it a label that describes what we’re testing.
  3. Arrange: We set up the data we’ll use in our test.
  4. Act: We run the function we’re testing.
  5. Assert: We check if the result is what we expected.

Grouping related tests

As you write more tests, you’ll want to keep them organized.

This is where describe comes in. It helps you group related tests together:

javascript
	import { expect, describe, it } from "vitest";
	import { functionToTest } from "./yourModule";
	
	describe("functionToTest", () => {
	  it("description of what the test is checking", () => {
	    // Arrange: Set up the test data
	    const input = "some input";
	    const expectedOutput = "expected output";
	
	    // Act: Call the function being tested
	    const result = functionToTest(input);
	
	    // Assert: Check if the result matches the expected output
	    expect(result).toBe(expectedOutput);
	  });
	
	  it("a different description of what the test is checking", () => {
	    // Arrange: Set up the test data
	    const input = "some input";
	    const expectedOutput = "expected output";
	
	    // Act: Call the function being tested
	    const result = functionToTest(input);
	
	    // Assert: Check if the result matches the expected output
	    expect(result).toBe(expectedOutput);
	  });
	});

Think of describe like creating a folder to keep similar tests together. In this example:

  • All tests are related to the functionToTest function
  • Each test checks a different scenario
  • The structure makes it easy to understand what we’re testing

Understanding Matchers

When we write tests, we need to check if our results are what we expect.

Vitest provides special functions called “matchers” to do these checks. Here are the most common ones:

Basic Matchers

javascript
	// toBe: Use for simple values (numbers, strings, booleans)
	expect(2 + 2).toBe(4);
	expect("hello").toBe("hello");
	expect(true).toBe(true);
	
	// toEqual: Use for objects and arrays
	const user = { name: "John", age: 30 };
	expect(user).toEqual({ name: "John", age: 30 });
	
	const numbers = [1, 2, 3];
	expect(numbers).toEqual([1, 2, 3]);

Truthiness Matchers

javascript
	// toBeNull: Checks if something is null
	expect(null).toBeNull();
	
	// toBeDefined: Checks if something has been defined
	let name = "John";
	expect(name).toBeDefined();
	
	// toBeUndefined: Checks if something is undefined
	let age;
	expect(age).toBeUndefined();
	
	// toBeTruthy: Checks if something is true-like
	expect("hello").toBeTruthy();
	expect(1).toBeTruthy();
	
	// toBeFalsy: Checks if something is false-like
	expect("").toBeFalsy();
	expect(0).toBeFalsy();

Number Matchers

javascript
	// toBeGreaterThan: Checks if a number is bigger
	expect(10).toBeGreaterThan(5);
	
	// toBeLessThan: Checks if a number is smaller
	expect(5).toBeLessThan(10);
	
	// toBeGreaterThanOrEqual: Checks if a number is bigger or equal
	expect(10).toBeGreaterThanOrEqual(10);
	
	// toBeLessThanOrEqual: Checks if a number is smaller or equal
	expect(5).toBeLessThanOrEqual(5);

String Matchers

javascript
	// toMatch: Checks if a string matches a pattern (regex)
	expect("hello@example.com").toMatch(/@/);
	expect("hello world").toMatch(/world/);
	
	// toContain: Checks if a string contains another string
	expect("hello world").toContain("hello");

Array Matchers

javascript
	const shoppingList = ["milk", "bread", "eggs"];
	
	// toContain: Checks if an array contains an item
	expect(shoppingList).toContain("milk");
	
	// toHaveLength: Checks the length of an array
	expect(shoppingList).toHaveLength(3);

Object Matchers

javascript
	const user = {
	  name: "John",
	  age: 30,
	  email: "john@example.com",
	};
	
	// toHaveProperty: Checks if an object has a property
	expect(user).toHaveProperty("name");
	
	// toHaveProperty with value: Checks property and its value
	expect(user).toHaveProperty("name", "John");

Not Matcher

You can use .not before any matcher to check for the opposite:

javascript
	// Checking that something is NOT equal
	expect(2 + 2).not.toBe(5);
	
	// Checking that an array does NOT contain something
	const numbers = [1, 2, 3];
	expect(numbers).not.toContain(4);

What We Learned

In this lesson, we:

  • Learned how to write basic unit tests
  • Understood the Arrange-Act-Assert pattern
  • Learned how to group tests with describe
  • Discovered different types of matchers and when to use them

Remember:

  • Always give your tests clear descriptions

  • Use the right matcher for your test case

    • toBe() for simple values (numbers, strings, booleans)
    • toEqual() for objects and arrays
  • Group related tests together using describe

  • Follow the Arrange-Act-Assert pattern