Week 5 - Problem solving

Problem solving methods

Basic algorithms

Big O notation

Logging

Debugging

Unit testing

AI

Practice

Assignment

Core program

Unit Testing with Vitest

Unit testing is the practice of checking whether small, individual pieces of your code (usually functions) work the way you expect. Instead of waiting for bugs to appear later, tests help you catch mistakes early and make changes confidently.

Vitest is a fast, modern testing framework built for JavaScript. It works similarly to Jest and integrates well with modern tooling.

Testing is not about proving your code is perfect — it’s about proving your assumptions are correct.


What is a unit test?

A unit test:

If something is wrong, the test tells you exactly where the problem is.

import { test, expect } from "vitest";
import { sum } from "./sum.js";

test("adds two numbers", () => {
  expect(sum(2, 3)).toBe(5);
});

This test checks that sum(2, 3) returns 5. If the function is broken, Vitest will tell you.

💡 Tests give you confidence when changing code, refactoring, or adding new features.


Why testing is important

Testing helps you:

Without tests, every change to the code is a gamble.

⚠️ Debugging without tests is like fixing a plane while flying it — risky and stressful.


Setting up Vitest

Install Vitest:

npm install --save-dev vitest

Add this to your package.json:

{
  "scripts": {
    "test": "vitest"
  }
}

Run tests:

npm test

💡 Vitest automatically finds all files ending with .test.js or .spec.js.


The AAA pattern: Arrange – Act – Assert

Almost every unit test follows this structure.

Arrange

Set up data, inputs, or environment.

Act

Run the function you are testing.

Assert

Check that the result matches your expectation.

Example:

import { test, expect } from "vitest";
import { multiply } from "./math.js";

test("multiplies two numbers", () => {
  // Arrange
  const a = 4;
  const b = 5;

  // Act
  const result = multiply(a, b);

  // Assert
  expect(result).toBe(20);
});

This pattern makes your tests clean, readable, and easy to understand.


Writing your first tests

Imagine this function:

export function findMax(numbers) {
  let max = numbers[0];

  for (const n of numbers) {
    if (n > max) max = n;
  }

  return max;
}

We can test it like this:

import { test, expect } from "vitest";
import { findMax } from "./findMax.js";

test("finds the largest number", () => {
  expect(findMax([1, 5, 3])).toBe(5);
});

test("works with negative numbers", () => {
  expect(findMax([-10, -3, -20])).toBe(-3);
});

💡 Good tests include normal cases, edge cases, and weird cases.


Testing edge cases

Some situations don’t appear often but can break your code.

Examples:

test("handles a single-item array", () => {
  expect(findMax([7])).toBe(7);
});

test("handles an empty array", () => {
  expect(findMax([])).toBe(undefined); // or decide your behavior
});

You decide what your function should do — tests make that decision explicit.


Common matchers (expect methods)

Vitest provides many powerful assertions:

Simple values:

expect(result).toBe(10);
expect(name).toBe("Alice");

Object comparison:

expect(user).toEqual({ name: "Sam", age: 20 });

Truthiness:

expect(isLoggedIn).toBeTruthy();
expect(errors.length).toBeFalsy();

Arrays:

expect(colors).toContain("blue");

Error checking:

expect(() => divide(10, 0)).toThrow();

💡 toBe compares primitive values. toEqual compares objects or arrays.


Testing functions with different inputs

You can test the same function with different data:

test("returns true for even numbers", () => {
  expect(isEven(4)).toBe(true);
  expect(isEven(10)).toBe(true);
});

test("returns false for odd numbers", () => {
  expect(isEven(3)).toBe(false);
});

This gives stronger confidence that your function behaves correctly.


When tests fail

A failing test is not a bad sign — it's a useful signal.

Vitest shows:

Use failures to guide your debugging.

💡 A failing test is just your program telling you the truth.


Organizing test files

Good structure:

src/
  math.js
  findMax.js
tests/
  math.test.js
  findMax.test.js

But placing test files next to code also works:

math.js
math.test.js

Choose whichever keeps your project clean.


Test-driven development (optional concept)

Some developers write tests before the code.

Why?