JAVASCRIPT • DEBUGGING AND TESTING

Writing Test Cases — Write simple test cases for functions using assertions (conceptual)

Learn how assertions let you automatically verify that your JavaScript functions produce the correct output every time.

Historical Context & Motivation

Imagine you build a calculator app for a class project. It works perfectly on Monday, but after you add a new feature on Tuesday, the addition function suddenly returns wrong answers. You didn't mean to break anything — you just didn't have a way to automatically check that your old code still worked. This is exactly the problem that test cases solve. A test case is a small piece of code that runs your function with known inputs and checks whether the output matches what you expect.

The idea of automated testing has evolved over decades as software grew more complex. Early programmers tested their code by manually running it and checking the output with their own eyes. As programs grew from hundreds to millions of lines, manual testing became impossible. Developers needed machines to do the checking for them, which led to the concept of assertions — statements that declare "this condition must be true, or something is wrong."

1960s
Manual Debugging Era
Programmers tested code by reading printouts and checking results by hand. Bugs were found only when the program visibly crashed or produced obviously wrong output.
1979
assert() in C Language
The C programming language introduced the assert() macro, letting developers embed automatic checks directly inside their code. This idea influenced every language that followed.
1999
JUnit & Unit Testing Movement
Kent Beck and Erich Gamma released JUnit for Java, popularizing the practice of writing small, focused test cases for individual functions — a practice called unit testing.
2010s
JavaScript Testing Frameworks
Tools like Mocha, Jasmine, and Jest brought powerful assertion-based testing to JavaScript, making it standard practice for web developers worldwide.
Today
Testing as a Requirement
Modern software teams require test cases before code can be merged. Continuous Integration (CI) systems run all tests automatically on every code change.

The core question that testing answers is deceptively simple: "Does my function actually do what I think it does?" Without test cases, you're relying on hope. With them, you have proof.

Core Principles & Definitions

Before writing test cases, you need to understand a few foundational ideas. A test case is a small, self-contained piece of code that calls a function with specific inputs and checks whether the output matches the expected result. An assertion is the actual check — a statement that says "I assert this condition is true." If the condition is true, the test passes silently. If it's false, the test fails loudly, alerting you to the problem.

1

Test Case

A small block of code that exercises a function with known inputs and compares the actual output to the expected output. Each test targets one specific behavior.
2

Assertion

A statement that evaluates a condition and throws an error if the condition is false. Think of it as a checkpoint: console.assert(2 + 2 === 4) passes because the math is correct.
3

Expected vs. Actual

The expected value is what you believe the function should return. The actual value is what it really returns. A test passes when expected === actual.
4

Edge Case

An unusual or extreme input — like zero, negative numbers, empty strings, or very large values. Good test suites always include edge cases because bugs love to hide there.
5

Test Suite

A collection of related test cases grouped together. Running a test suite gives you a quick report of how many tests passed and how many failed.
✦ KEY TAKEAWAY
Think of assertions like a teacher's answer key. When you grade a student's test, you compare their answer to the correct one. An assertion does the same thing — it compares your function's answer to the correct one. If they match, the function "passes." If not, you know exactly which question (function behavior) went wrong.

Visual Explanation — The Anatomy of a Test Case

This diagram shows the three-step pattern every test case follows: Arrange your inputs, Act by calling the function, and Assert that the result matches your expectation. The bottom shows two possible outcomes: a green pass or a red fail.

The diagram above illustrates the Arrange-Act-Assert pattern (sometimes called AAA), which is the most common way to structure a test case in any programming language. In the Arrange step, you set up the inputs and any data your function needs. In the Act step, you call the function and capture its return value. In the Assert step, you use an assertion to compare the actual return value against the expected value. If they match, the test passes quietly. If they don't, an error message tells you exactly what went wrong.

How Assertions Work in JavaScript

JavaScript gives you several ways to write assertions. The simplest is the built-in console.assert() method. It takes a condition as its first argument and an optional error message as its second. If the condition evaluates to true, nothing happens — silence means success. If the condition evaluates to false, the message is printed to the console as an error.

console.assert() Syntax

ASSERTION SYNTAX
console.assert(condition, errorMessage)
condition — any expression that evaluates to true or false. errorMessage — a string describing what went wrong, displayed only when the condition is false.

You can also write your own simple assertion function. This gives you more control over the output and helps you understand what's happening under the hood. Here's the basic logic:

CUSTOM ASSERTION LOGIC
function assert(condition, message) { if (!condition) throw new Error(message); }
If the condition is falsy (false, 0, null, undefined, etc.), the function throws an error with your custom message. If the condition is truthy, execution continues normally — the test passes.

A critical detail is the use of strict equality (===) rather than loose equality (==) in your assertions. Strict equality checks both the value and the type, which prevents subtle bugs. For example, 5 == '5' is true in JavaScript (loose equality coerces types), but 5 === '5' is false (strict equality does not). Always use === in your test assertions to catch type-related bugs.

💡 Why Not Just Use console.log()?
You could test by printing results with console.log() and reading them yourself, but this doesn't scale. If you have 50 functions, you'd need to manually read 50 outputs every time you change your code. Assertions automate this: they stay silent when things work and scream when they don't. You only need to pay attention when something breaks.

Types of Test Cases You Should Write

A single test case only tells you about one specific input. To gain confidence that a function truly works, you need to write multiple test cases covering different scenarios. Think of it like studying for a test — reviewing just one practice problem isn't enough. You need to practice with normal cases, edge cases, and error cases.

The three categories of test cases shown above — normal, edge, and error — together form a comprehensive test suite for the isEven() function.

Normal cases test the everyday, expected behavior. These are the inputs your function was primarily designed to handle. Edge cases test the boundaries — zero, negative numbers, extremely large values, or the first and last items in a list. Error cases test what happens when someone passes the wrong type of data or missing values. A function that handles all three categories gracefully is a robust function.

  • Normal cases — Test with common, expected inputs that represent typical usage.
  • Edge cases — Test with boundary values like 0, empty strings, and negative numbers.
  • Error cases — Test with invalid inputs like wrong data types, null, or undefined.

Worked Example — Testing a gradeCalculator Function

Let's walk through a complete example. Suppose you write a function called getLetterGrade(score) that takes a numerical score (0–100) and returns a letter grade: 'A' for 90–100, 'B' for 80–89, 'C' for 70–79, 'D' for 60–69, and 'F' for anything below 60. We'll write a full set of test cases using assertions.

Writing Test Cases for getLetterGrade(score)
1
Step 1 — Define the FunctionFirst, here's the function we want to test: function getLetterGrade(score) { if (score >= 90) return 'A'; if (score >= 80) return 'B'; if (score >= 70) return 'C'; if (score >= 60) return 'D'; return 'F'; } Our job is to write assertions that verify this function works correctly for a variety of inputs.
2
Step 2 — Write Normal Case TestsWe test typical scores from each grade range: console.assert(getLetterGrade(95) === 'A', 'Score 95 should be A'); console.assert(getLetterGrade(85) === 'B', 'Score 85 should be B'); console.assert(getLetterGrade(75) === 'C', 'Score 75 should be C'); console.assert(getLetterGrade(65) === 'D', 'Score 65 should be D'); console.assert(getLetterGrade(45) === 'F', 'Score 45 should be F');
All five assertions pass — one for each grade range.
3
Step 3 — Write Edge Case TestsNow we test the boundary values — the exact scores where grades change: console.assert(getLetterGrade(90) === 'A', 'Boundary: 90 should be A'); console.assert(getLetterGrade(89) === 'B', 'Boundary: 89 should be B'); console.assert(getLetterGrade(0) === 'F', 'Boundary: 0 should be F'); console.assert(getLetterGrade(100) === 'A', 'Boundary: 100 should be A'); These boundary tests are crucial because off-by-one errors — using > instead of >=, for example — are among the most common bugs.
Boundary tests confirm grades transition correctly at 90, 80, 70, and 60.
4
Step 4 — Write Error Case TestsFinally, we consider unusual inputs. What if someone passes a negative score or a score above 100? Our current function doesn't handle these, which the tests reveal: console.assert(getLetterGrade(-5) === 'F', 'Negative score should be F'); This test passes because our function returns 'F' for anything below 60. But we might want to add input validation in the future.
Error case tests reveal how the function handles unexpected inputs and may inspire improvements.
5
Step 5 — Interpret ResultsIf all assertions pass, the console remains silent — no news is good news. If any assertion fails, you'll see a red error message in the console, like: Assertion failed: Boundary: 90 should be A This tells you exactly which test failed, so you can fix the bug quickly.
A complete test suite for getLetterGrade now covers normal, edge, and error cases — giving you high confidence the function works.

Strengths & Limitations of Assertion-Based Testing

Assertion-based testing is a powerful tool, but like any tool, it has both strengths and limitations. Understanding these will help you use assertions effectively and know when to reach for more advanced testing approaches.

Strengths and limitations of assertion-based testing in JavaScript
AspectStrengths ✓Limitations ✗
SimplicityEasy to write and understand; no external libraries needed for basic assertions.For complex comparisons (like comparing objects or arrays), simple === doesn't work.
SpeedTests run in milliseconds, so you can run them every time you save your code.console.assert() doesn't stop execution — later tests may run with corrupted state.
Bug DetectionCatches regressions immediately when you change code, preventing old bugs from returning.Tests can only check scenarios you think of. Unknown edge cases remain untested.
DocumentationTest cases serve as living documentation showing how a function is intended to be used.console.assert() provides minimal output — no summary, no count of pass/fail.
ScopePerfect for testing pure functions (functions that return a value based only on their inputs).Can't easily test user interface interactions, network requests, or visual output.
✦ KEY TAKEAWAY
Assertions are like a smoke detector in your house. They won't prevent a fire (bug) from starting, and they can't detect every possible danger — but when something does go wrong, they alert you immediately so you can fix it before it spreads. For more advanced detection, you'd upgrade to a full security system (a testing framework like Jest), but a smoke detector is a great first line of defense.

Connection to Professional Testing Frameworks

The conceptual assertions you've learned here are the foundation for every professional testing framework. When you're ready to level up, tools like Jest, Mocha, and Jasmine provide richer assertion libraries, automatic test discovery, detailed reports, and the ability to test asynchronous code. But the core idea remains the same: call a function, check the result.

Comparing basic assertions to a professional testing framework
Featureconsole.assert()Jest (Professional)
Syntaxconsole.assert(x === 5)expect(x).toBe(5)
OutputError in console only on failureDetailed pass/fail report with colors
Object comparisonNot supported nativelyexpect(obj).toEqual({a: 1})
Setup requiredNone — built into every browserInstall via npm, configuration file
Best forLearning, quick checks, small projectsTeam projects, production code, CI/CD

Notice how Jest's expect(x).toBe(5) reads almost like English: "expect x to be 5." This is the same concept as console.assert(x === 5) — just wrapped in a more descriptive and powerful syntax. Once you understand assertions conceptually, transitioning to Jest or any other framework feels natural because you already know the core pattern: arrange, act, assert.

🚀 Looking Ahead
Professional developers also use techniques like Test-Driven Development (TDD), where you write the test cases before writing the function. This forces you to think clearly about what the function should do before you start coding. It's a powerful discipline that builds on everything you're learning now.

Practice Problems

PROBLEM 1 — CONCEPTUAL
In your own words, explain why writing test cases with assertions is more reliable than manually checking output with console.log(). What specific advantage does an assertion provide that a log statement does not?
PROBLEM 2 — BASIC
Given a function function double(n) { return n * 2; }, write three assertion statements using console.assert() that test this function with a positive number, zero, and a negative number.
PROBLEM 3 — INTERMEDIATE
A classmate writes the function function max(a, b) { if (a > b) return a; return b; } and claims it works perfectly. Write a set of at least four assertion-based test cases that would thoroughly test this function. Include normal cases, edge cases, and at least one error case. For each test, explain why you chose that particular input.
PROBLEM 4 — APPLIED
You're building a simple password validator function: function isValidPassword(pw) { return pw.length >= 8; } Your teammate later changes it to also require at least one digit. Write assertion-based test cases that would have caught the change in behavior — meaning, tests that would pass before the change but might fail after, alerting the team that something changed.
PROBLEM 5 — CRITICAL THINKING
Consider this claim: "If all my test cases pass, my function is bug-free." Is this statement true or false? Explain your reasoning, and describe a scenario where all tests pass but the function still contains a bug.

Lesson Summary

A test case is a small piece of code that verifies a function produces the correct output for a specific input. The core mechanism is the assertion — a statement that checks whether a condition is true and reports an error if it's false. In JavaScript, you can use the built-in console.assert() method or write your own assertion function. Every test case follows the Arrange-Act-Assert pattern: set up your inputs, call the function, and check the result using strict equality (===).

A strong test suite covers three categories: normal cases for typical inputs, edge cases for boundary values like zero and negatives, and error cases for invalid or unexpected inputs. While assertion-based testing cannot prove a function is completely bug-free, it dramatically increases confidence in your code and catches regressions — bugs that reappear when you modify existing code. These skills form the foundation for professional testing frameworks like Jest that you'll encounter as you advance.

Varsity Tutors • JavaScript • Writing Test Cases — Write simple test cases for functions using assertions (conceptual)