Exercise: Ticket Booth Tests

Write the Vitest tests for a movie theater's working ticket booth, from its prices and discounts to its page, and prove they're good by catching three bugs the check plants in the code.

Freelancer · JavaScript · Unit Testing · Updated

The Starlight Cinema's ticket booth works today. Matinees are $9 and evening shows $14, students save $2 and seniors $3, groups of 8 or more save $1 on every ticket, and the booth page adds up each order. Nobody wrote tests for it, though, so the next person who changes a price could break the booth and not find out until a customer does. This time the code is finished and the tests are yours to write. And to show your tests are worth having, a check plants three real bugs in the code, one at a time, and tells you whether your tests caught each one.

Before you start

This exercise is a Vite project with Vitest and jsdom, at the exact versions the lessons use (Vite 8.3.5, Vitest 5.0.5, jsdom 30.1.3). You need Node.js 24 or later; node --version in a terminal shows yours.

Get the exercise code

In the Exercise box beside the lesson, choose Get the starter on GitHub to open its GitHub repository. Click Code, then Download ZIP, and unzip it into a folder of your own. Or clone it with Git. Open the folder in VS Code, open its terminal with Terminal > New Terminal, and run npm install once.

The folder holds:

  • src/tickets.js: the booth's prices and seats, five working functions with a comment and examples above each. Ready to use: don't change it.
  • src/booth.js: the booth page's code. Pick who a ticket is for, click Add ticket, and the list and the total update. Ready to use.
  • src/tickets.test.js and src/booth.test.js: your tests. Each has one worked example to show the shape.
  • index.html and the rest of src/: the page itself, ready to use. npm run dev opens it.
  • check/: the check, with its three planted-bug copies of tickets.js. You don't need to change it, and it's more fun not to read the copies until you're done.
  • README.md: the list of behaviors your tests should cover.

What to do

Every function in tickets.js has a comment that says what it does, with examples:

/*
 * The total for a group: guests is an array of guest types.
 * Groups of 8 or more get $1 off every ticket.
 *
 * groupTotal(19, ['adult', 'student']) -> 26
 * groupTotal(19, []) -> 0
 */
  1. Read the README's list of behaviors. There are twelve, for showPrice, ticketPrice, groupTotal, bookSeats, loadOpenShowtimes and the booth page.
  2. Write the tests for tickets.js in src/tickets.test.js: one describe group per function, and one it per behavior, named in plain words.
  3. Test the errors and the async function. ticketPrice and bookSeats throw on bad input, and loadOpenShowtimes waits for a server: hand it a stand-in of your own.
  4. Write the tests for the page in src/booth.test.js. The file already runs in jsdom and builds the page before every test.

Check your work

While you write, keep the tests running:

npm test

Every test you write should pass, because the code works. If one fails, work out the expected value again by hand: the test is the part to fix. Press q to stop watching.

Then see how good your tests are:

npm run check

It runs your tests four times: once on the working tickets.js, then once on each of three copies with a bug planted in it. One copy has an off-by-one (a limit that's out by one), one a wrong discount, and one a missing await. A bug counts as caught when at least one of your tests fails on its copy. At the start, check 1 passes (the worked examples pass) and checks 2 to 4 fail: that's expected, because two tests can't catch three bugs yet. A line that fails says what kind of test it's missing.

Hints

  • Work out each expected value by hand, from the comments in tickets.js. A test that copies whatever the code returns proves nothing.
  • Bugs hide at the edges. For a rule such as "8 or more", test 7 and 8, not only 20. Testing Edge Cases, Errors and Async Code has the list of edges to look for.
  • Test every guest type, at a matinee and at an evening show. A test for students alone can't tell whether seniors are charged right.
  • An error test needs an arrow: expect(() => ...).toThrow(...). An async test needs async and await, with resolves or rejects.
  • On the page, set the picker's value, click Add ticket with dispatchEvent, and read textContent, as in Testing a Page with jsdom.

When you're done

Every test is green in npm test, and npm run check shows four check marks:

✔ 1) your tests run and pass on the working tickets.js (0.5402ms)
✔ 2) your tests catch planted bug 1, an off-by-one (1048.7512ms)
✔ 3) your tests catch planted bug 2, a wrong discount (1070.6139ms)
✔ 4) your tests catch planted bug 3, a missing await (1053.9799ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0

Now open the three copies in check/planted-bugs/ and find each bug. Which of your tests caught it? If you had to delete all but five of your tests, which five would you keep, and why?

So what

Anyone can write tests that pass. Tests that fail when the code is wrong are the ones that protect customers, and planting a bug on purpose to see whether your tests notice is how professional teams check their tests too. It's called mutation testing, and there are tools that plant hundreds of bugs for you.

Key terms

Related