From FizzBuzz to React, Practical Test Driven Development in JavaScript

Lee Cheneler
6 min readJun 21, 2019

--

This article presumes you have done some unit testing before using JavaScript and have a basic grasp of the tooling. Complete code for this blog can be found here. ✌️

Red, Greed, Refactor

Test driven development (TDD) is a powerful tool in any developers tool belt. When using TDD we get a number of benefits; natural 100% test coverage, lack of unnecessary code, code that is inherently testable and many more. However the purpose of this article is not to sell you on TDD, many people have already done a fantastic job of that! A quick search of the web will yield many great articles explaining why TDD is good and what advantages it brings with it.

What this article aims to do is provide clear cut examples of test driven development with real tests and real code. You should be able to copy the examples into your IDE, and run them at each stage to see the outcome for yourself.

Red ❌, green ✅, refactor 🔵

TDD is made up of 3 stages of development.

  1. Write a test and run it, it should fail. This is our red ❌ stage.
  2. Write the minimum amount of code to make the test pass, no matter how quick and dirty that code is. This should result in 100% coverage, any less and we’ve written more code than was required for the test to pass! This is our green ✅ stage.
  3. Refactor the implementation until it meets your coding standards. You won’t always need to do this. The important thing is that you should be able to refactor the implementation with confidence because it already has a test. Like in the previous stage we also don’t want coverage to drop below 100% because this means we’ve added unnecessary code. This is our blue 🔵 stage.

Then sit back and bask in the glory of you beautiful, fully tested code. 😎

FizzBuzz, the basics of red ❌ and green ✅

FizzBuzz is a great function to write using TDD whilst you are starting out. It is small, has multiple stages of functionality and clearly shows the red and green stages of test driven development. It is also a favourite for many companies when discussing TDD in interviews with candidates and is quite a common pairing exercise.

This example was built using the testing library Jest. You can copy the new code in each stage into a new JavaScript project and see the tests going red and green as you follow the examples.

❌ Test 1 fails

// fizzbuzz.test.jsimport { fizzbuzz } from "./fizzbuzz";it("should return the numbers passed in that are not divisible by 3 or 5", () => {
expect(fizzbuzz(1)).toBe(1);
expect(fizzbuzz(2)).toBe(2);
expect(fizzbuzz(4)).toBe(4);
expect(fizzbuzz(7)).toBe(7);
expect(fizzbuzz(8)).toBe(8);
expect(fizzbuzz(11)).toBe(11);
});

✅ Test 1 passes

// fizzbuzz.jsexport const fizzbuzz = num => num

🔵 Refactor?

At this point we’d look at our implementation code now the test passes and do any refactoring to clean it up. You won’t always need to refactor your implementation though. For instance we don’t need to refactor this code as it is already a clean and simple solution. 👍

❌ Test 2 fails

// fizzbuzz.test.js...it("should return 'fizz' if the the number passed in is divisible by 3", () => {
expect(fizzbuzz(3)).toBe("fizz");
expect(fizzbuzz(6)).toBe("fizz");
expect(fizzbuzz(9)).toBe("fizz");
expect(fizzbuzz(12)).toBe("fizz");
});

✅ Test 2 passes

// fizzbuzz.jsexport const fizzbuzz = num => {
if (num % 3 === 0) {
return "fizz";
}
return num;
};

❌ Test 3 fails

// fizzbuzz.test.js...it("should return 'buzz' if the the number passed in is divisible by 5", () => {
expect(fizzbuzz(5)).toBe("buzz");
expect(fizzbuzz(10)).toBe("buzz");
expect(fizzbuzz(20)).toBe("buzz");
});

✅ Test 3 passes

// fizzbuzz.jsexport const fizzbuzz = num => {
if (num % 3 === 0) {
return "fizz";
}
if (num % 5 === 0) {
return "buzz";
}
return num;
};

❌ Test 4 fails

// fizzbuzz.test.js...it("should return 'fizzbuzz' if the the number passed in is divisible by 3 and 5", () => {
expect(fizzbuzz(15)).toBe("fizzbuzz");
expect(fizzbuzz(30)).toBe("fizzbuzz");
expect(fizzbuzz(45)).toBe("fizzbuzz");
});

✅ Test 4 passes

// fizzbuzz.jsexport const fizzbuzz = num => {
if (num % 15 === 0) {
return "fizzbuzz";
}

if (num % 3 === 0) {
return "fizz";
}

if (num % 5 === 0) {
return "buzz";
}
return num;
};

Now we have a really well tested implementation of FizzBuzz that any interviewer would be pleased to see a candidate implement!

React counter, an introduction to refactoring

One of the most important benefits of TDD is being able to refactor with confidence. To show that off, we’re going to build a counter component using the React library. We will build it using one style of React component, then we’ll refactor it to a more modern style of component without fear that anything will break.

This example was built using Jest, React, React Testing Library and Jest DOM. You can copy the new code in each stage into a new JavaScript project and see the tests going red and green as you follow the examples.

❌ Test 1 fails

// counter.test.jsimport React from "react";
import { render, fireEvent } from "@testing-library/react";
import "@testing-library/react/cleanup-after-each";
import "jest-dom/extend-expect";
import { Counter } from "./counter";
it("should display the start count as 0", () => {
const { getByTestId } = render(<Counter />);
expect(getByTestId("count")).toHaveTextContent("Count: 0");
});

✅ Test 1 passes

// counter.jsimport React, { Component } from "react";export class Counter extends Component {
render() {
return <span data-testid="count">Count: 0</span>;
}
}

❌ Test 2 fails

// counter.test.js...it("should increase the count by one when the user increments the count", () => {
const { getByTestId, getByText} = render(<Counter />);
fireEvent.click(getByText("Increment");
expect(getByTestId("count")).toHaveTextContent("Count: 1");
fireEvent.click(getByText("Increment");
expect(getByTestId("count")).toHaveTextContent("Count: 2");
});

✅ Test 2 passes

// counter.js...export class Counter extends Component {
constructor() {
super();
this.increment = this.increment.bind(this);
this.state = {
count: 0
};
}
increment() {
this.setState({
count: this.state.count + 1,
});
};
render(){
return (
<>
<span data-testid="count">Count: {this.state.count}</span>
<button type="button" onClick={this.increment}>
Increment
</button>
</>
);
};
}

Now we have a fully functional counter component. It displays the current count and you can increase the count by clicking the increment button. This is great, however the world of modern web development changes so quickly! For instance, recently the React team released a new way to manage state within React components. Because we want to be using the leading practices whenever we have the chance we can refactor this component to use the new API. This is our blue stage!

🔵 Refactor with confidence

// counter.jsimport React, { useState } from "react";export const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<>
<span data-testid="count">Count: {count}</span>
<button type="button" onClick={increment}>
Increment
</button>
</>
);
};

Now our counter component is using the latest React APIs, and our existing tests still pass as it exhibits the same behaviour.

Conclusion

Test driven development can be daunting at first. Like most skills worth having it takes time and practice to get good at, I’m still learning all the time and improving and getting better at TDD as I’m still not perfect, and never will be. But it has become a major part of my workflow now and I’ve seen a decline in bugs and development time due to adopting it.

Hopefully you’ve been able to follow these examples and run them yourself to see the tests going from red ❌ to green ✅. I hope that by showing practical, step by step examples of it in action the barrier to entry is lowered and you feel confident to try it for yourself on your projects

Complete code for this blog can be found here. ✌️

--

--