A unit test checks one small piece of logic in isolation.
- Input goes in → expected output comes out
- Runs in milliseconds
- No network, no database, no side effects
You're going to test a function that validates email addresses. This is a good first example because:
- Everyone already has an intuition for what a valid email looks like
- There are surprising edge cases that easy implementations miss
- A bug in production breaks sign-up forms, password resets, and notifications
cd 01-unit-tests
npm install
npm testExpected output: You should see 2 passing tests and 2 failing tests.
That's intentional. The implementation in src/emailValidator.js is incomplete.
Open package.json in this folder. You'll see this:
{
"scripts": {
"test": "vitest run --reporter=verbose",
"test:watch": "vitest"
}
}npm test is a shortcut. All it does is look up the "test" key inside "scripts" and run that command — in this case vitest run --reporter=verbose.
Vitest is the testing framework. It does the actual work:
- Looks for files ending in
.test.js - Runs every
test(...)block it finds - Prints which passed and which failed
You could run npx vitest run --reporter=verbose directly and get the exact same result. npm test is just a convenient shorthand that works the same way in every project, regardless of which framework is being used underneath.
01-unit-tests/
├── src/
│ └── emailValidator.js ← the function you'll fix
└── tests/
└── emailValidator.test.js ← the tests (read these first)
Open both files before starting. Read the tests to understand what the function is supposed to do.
Run npm test and look at which tests fail.
For each failing test:
- Read the test name — it describes what the function should do
- Look at
emailValidator.js— what does it actually do? - Fix the function
- Run
npm testagain
Checkpoint: All 4 starter tests should pass before moving on.
Tip:
npm run test:watchre-runs tests every time you save a file — useful for fast feedback.
Open tests/emailValidator.test.js and find the Exercise 1 describe block.
Uncomment the tests one at a time. For each one:
- Uncomment the test
- Run
npm test - If it fails, update
emailValidator.jsto make it pass - Make sure earlier tests still pass
Important: When you fix a new edge case, you might accidentally break an older test. This is called a regression. Fix it before moving on.
Checkpoint: All uncommented tests are green.
Find the Exercise 2 describe block in the test file.
Write at least 3 test cases. Think about inputs that could trick or break your implementation. There are hints in the comments.
| Term | What it means |
|---|---|
describe |
Groups related tests together |
test |
A single test case |
expect |
Makes an assertion about a value |
.toBe(x) |
Checks that the value is exactly x |
toBeTruthy / toBeFalsy |
Checks truthiness |
| regression | A previously passing test that now fails after a change |
Look up how email validation actually works:
- The HTML
<input type="email">element uses a specific regex - The official spec (RFC 5322) is much more permissive than you'd expect
Questions to discuss:
- What valid emails does your implementation incorrectly reject?
- What invalid emails does it incorrectly accept?
- Is "perfect" validation worth the complexity? What's the alternative?