From FizzBuzz to React, Practical Test Driven Development in JavaScript

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 🔵

  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 ✅

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?

❌ 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

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

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. ✌️

JavaScript developer. Director@EnzymeSoftware

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store