Skip to Content
Course content

125: Test-Driven Development Basics in JavaScript

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I watched a teammate spend an entire Friday night hunting a bug in a tax calculation utility. He had changed one conditional to handle a new tax region in Canada, but in doing so, he accidentally inverted the logic for the existing US states. He didn't realize he'd broken the core functionality until a customer reported a checkout error on Saturday morning. He spent hours manually entering values into a form, refreshing the page, and praying he'd find the edge case. That's the exact nightmare Test-Driven Development (TDD) is designed to kill. When you have a suite of tests, you don't "hope" it still works; you know it does because the tests tell you in milliseconds.

Flipping the Script with Red-Green-Refactor

Most of us are taught to write code and then "test it" to see if it works. TDD flips that on its head. You write the test first, watch it fail, and then write the bare minimum amount of code required to make that test pass. I call this the "Red-Green-Refactor" loop. It sounds counterintuitive—why write a test for something that doesn't exist yet? Because it forces you to think about the interface and the expected outcome before you get bogged down in the implementation details. It stops you from over-engineering features you don't actually need.

Building a Discount Calculator from the Outside In

Let's look at how this actually feels in practice. Imagine we need a function that applies a discount code to a cart total. Instead of writing the logic, we start with a test. I'll use a generic assertion style here that you'll see in frameworks like Jest or Mocha.

// The Test (The "Red" phase)
test('should apply 10% discount for code SAVE10', () => {
  const result = applyDiscount(100, 'SAVE10');
  expect(result).toBe(90);
});

If you run this right now, it will crash because applyDiscount isn't defined. That's your "Red" state. Now, we move to "Green." The goal here isn't to write the perfect, scalable function; it's to make the test pass as quickly as possible. I might even hardcode the return value just to prove the test works.

// The Implementation (The "Green" phase)
function applyDiscount(price, code) {
  if (code === 'SAVE10') {
    return price * 0.9;
  }
  return price;
}

Once the test turns green, we have a safety net. If we want to add a 'SAVE20' code or handle invalid codes, we repeat the cycle: write a failing test for the new requirement, then write the code to satisfy it. You're building the feature in tiny, verifiable increments.

The Art of Refactoring without Fear

The final part of the loop is Refactoring. This is where you clean up the code—removing duplication, improving variable names, or optimizing performance. In a traditional workflow, refactoring is terrifying because you might break something. In TDD, refactoring is a breeze. If you change a for loop to a .reduce() and the tests stay green, you haven't broken the contract. If they turn red, you know exactly what you messed up and can undo it in seconds.

I'll be honest: TDD feels slow at first. You'll feel like you're writing twice as much code. But you'll save that time tenfold by not spending your weekends debugging regressions that should have been caught by a script.




📋 Practical Task

Build a Password Strength Validator using TDD

Your goal is to create a validatePassword(password) function that returns true if a password is strong and false otherwise. However, you must follow the TDD workflow strictly. Do not write the function logic until you have written a failing test for that specific requirement.

Implement the following requirements in order, following the Red-Green-Refactor cycle for each:

  • Requirement 1: The password must be at least 8 characters long. (Write a test that fails for a 7-character password, then make it pass).
  • Requirement 2: The password must contain at least one number. (Write a test that fails for a password with only letters, then make it pass).
  • Requirement 3: The password must contain at least one special character (e.g., !, @, #, $). (Write a test that fails for alphanumeric-only passwords, then make it pass).

For the final step, refactor your validatePassword function to be as concise as possible (perhaps using Regular Expressions) and ensure all your tests still pass.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.