Tests: unit and integration

Play this lesson

Caught before players see it

Kai adds a combo bonus to Hyper Shards, plays one quick round, and it looks fine. On Saturday, #bug-reports fills up: every alien is still worth 10 points, combo or not. Nobody had played long enough to build a combo.

A test is a small program that runs a piece of the game's code and checks that the answer is right. Studios write tests for three big reasons:

Prompt pick

Remy wants Generative AI Model Alpha to build the combo bonus properly this time. Which prompt makes the best use of tests?

  1. "Add a combo bonus to score.js. Make sure it works."
  2. "In score.js, combo x2 should double the points. Add a test for that, run npx vitest run, and fix the code until every test passes."
  3. "Add a combo bonus. If any tests fail, delete them so the run is green."
Show the answer

"In score.js, combo x2 should double the points. Add a test for that, run npx vitest run, and fix the code until every test passes."

It turns "done" into something Claude can check by itself: write the test, run it, read the failure, fix, repeat. "Make sure it works" leaves Claude to judge its own work, and deleting failing tests hides bugs instead of fixing them.

Three sizes of test

Tests come in sizes, from one function up to the whole game:

The testing pyramid

Picture a pyramid. The wide bottom is lots of unit tests, the middle is fewer integration tests, and the narrow top is a handful of end-to-end tests.

Why that shape? Small tests are fast: hundreds of unit tests finish in a second or two, while one end-to-end test needs a couple of seconds just to start a browser and load the game. They are precise: when one fails, it points at one function. And they are steady: fewer moving parts means fewer random failures.

Bolt says: Some teams draw a trophy instead, with a fat middle of integration tests, because those are closer to what players actually do. Both shapes agree: many fast tests, few slow ones.

More kinds you will hear about

Quick check

A good studio writes mostly end-to-end tests, because they are the most realistic kind.

Show the answer

False

They are realistic, but slow, sometimes random, and a failure doesn't say which function broke. The pyramid keeps most tests small and fast, with a few end-to-end tests on top.

Meet Vitest

Our studio writes unit and integration tests with Vitest. A test file has .test. in its name, like score.test.js. Here, scoreFor(1, 2) means one hit with a x2 combo:

describe('scoreFor', () => {
  test('combo x2 doubles points', () => {
    expect(scoreFor(1, 2)).toBe(20);
  });
});

describe groups related tests. test is one check with a name. expect compares the real answer with the right one. A pass shows green. A fail shows red, with the expected and received values side by side.

Bolt says: npx vitest starts watch mode: it waits, and reruns the related tests every time you save a file. npx vitest run runs every test once, then stops.

What makes a good test

Find the gap

damage.test.js
import { test, expect } from 'vitest';import { hit } from './damage.js'; test('laser takes 2 health', () => {  expect(hit(10, 2)).toBe(8);});test('health stops at 0', () => {  expect(hit(1, 5)).toBe(0);});

hit(health, damage) gives an alien's health after a hit. Last month, a power-up bug sent negative damage and the aliens healed. Which test should Kai add?

  1. Expect hit(10, -3) to be 10, so negative damage can never heal
  2. A copy of the laser test with the same numbers
  3. Expect the variable inside hit() to be called newHealth
Show the answer

Expect hit(10, -3) to be 10, so negative damage can never heal

Negative damage is an edge case, and a test for a fixed bug is a regression test, so the bug can never sneak back. A copy adds nothing, and a variable name is a detail: renaming it would fail the test without breaking the game.

Fakes: tests without the internet

The real leaderboard lives in Firebase, on the internet. An integration test that used it would be slow, would fail whenever the wifi drops, and would fill the real leaderboard with test runs.

So the test swaps in a fake, often called a mock: a stand-in that acts like the backend but keeps scores in a simple list. Vitest's vi.fn() makes a fake function that remembers every call, so the test can check expect(fake.save).toHaveBeenCalledWith('Kai', 4200).

Bolt says: Fakes keep tests fast, but players never play against fakes. Next lesson: robot players that open the real game in a real browser.

Fact-checked 9 October 2026