A really simple example of TDD in JavaScript
Tags: javascript, tdd
Published: 2020-05-30 15:15:55

A step by step introduction to Test Driven Development in JavaScript.

## Exercise

I am going to demonstrate TDD by completing [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz). I have chosen to show each step in JavaScript because most of my work so far has been in this language. However, the same concepts apply to every language (I am familiar with). The complete source code can be found on Github in [JavaScript](https://github.com/DeveloperDavo/fizz-buzz-js) or [Java](https://github.com/DeveloperDavo/fizzBuzz).

The exercise is complete when the following input:

```javascript
[1, 2, 3, 5, 6, 10, 15]
```

results in the following output:

```javascript
'1, 2, Fizz, Buzz, Fizz, Buzz, FizzBuzz'
```

## Things to keep in mind

When demonstrating this exercise I like to mention the following points:

- Don’t write any production code before you have a failing test
- Make each step as small and simple as possible.

## Implementation

Here is the starter code for the test:

```javascript
import fizzBuzz from './fizzBuzz'

describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz()).toBe(undefined)
  })
})
```

And here is the starter code for the implementation:

```javascript
export default function fizzBuzz() {}
```

Make sure the test is green!

For those of you following along with the source code, you can run the tests in watch mode with `npm test`.

### Red, green, red, green, ..., green

The first real assertion can be written as follows:

```javascript
describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz([1])).toBe('1')
  })
})
```

The following snippet will make the test pass:

```javascript
export default function fizzBuzz() {
  return '1'
}
```

How easy was that!

I then add another assertion to the test:

```javascript
describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz([1])).toBe('1')
    expect(fizzBuzz([1, 2])).toBe('1, 2')
  })
})
```

And fulfil it:

```javascript
export default function fizzBuzz(input) {
  return input.join(', ')
}
```

Here I implement Fizz when the entry is 3:

```javascript
describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz([1])).toBe('1')
    expect(fizzBuzz([1, 2])).toBe('1, 2')
    expect(fizzBuzz([1, 2, 3])).toBe('1, 2, Fizz')
  })
})
```

```javascript
export default function fizzBuzz(input) {
  return input
    .map((entry) => {
      if (entry === 3) {
        return 'Fizz'
      }

      return entry
    })
    .join(', ')
}
```

If you are not familiar with `map`, you could use a `for` loop instead:

```javascript
export default function fizzBuzz(input) {
  const result = []
  for (const entry of input) {
    if (entry === 3) {
      result.push('Fizz')
    } else {
      result.push(entry)
    }
  }
  return result.join(', ')
}
```

Then I implement Buzz when the entry is 5:

```javascript
describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz([1])).toBe('1')
    expect(fizzBuzz([1, 2])).toBe('1, 2')
    expect(fizzBuzz([1, 2, 3])).toBe('1, 2, Fizz')
    expect(fizzBuzz([1, 2, 3, 5])).toBe('1, 2, Fizz, Buzz')
  })
})
```

```javascript
export default function fizzBuzz(input) {
  return input
    .map((entry) => {
      if (entry === 3) {
        return 'Fizz'
      }

      if (entry === 5) {
        return 'Buzz'
      }

      return entry
    })
    .join(', ')
}
```

Here I implement Fizz if the entry is a *multiple* of 3:

```javascript
describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz([1])).toBe('1')
    expect(fizzBuzz([1, 2])).toBe('1, 2')
    expect(fizzBuzz([1, 2, 3])).toBe('1, 2, Fizz')
    expect(fizzBuzz([1, 2, 3, 5])).toBe('1, 2, Fizz, Buzz')
    expect(fizzBuzz([1, 2, 3, 5, 6])).toBe('1, 2, Fizz, Buzz, Fizz')
  })
})
```

```javascript
export default function fizzBuzz(input) {
  return input
    .map((entry) => {
      if (entry % 3 === 0) {
        return 'Fizz'
      }

      if (entry === 5) {
        return 'Buzz'
      }

      return entry
    })
    .join(', ')
}
```

The same for Buzz if the entry is a *multiple* of 5:

```javascript
describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz([1])).toBe('1')
    expect(fizzBuzz([1, 2])).toBe('1, 2')
    expect(fizzBuzz([1, 2, 3])).toBe('1, 2, Fizz')
    expect(fizzBuzz([1, 2, 3, 5])).toBe('1, 2, Fizz, Buzz')
    expect(fizzBuzz([1, 2, 3, 5, 6, 10])).toBe(
      '1, 2, Fizz, Buzz, Fizz, Buzz'
    )
  })
})
```

```javascript
export default function fizzBuzz(input) {
  return input
    .map((entry) => {
      if (entry % 3 === 0) {
        return 'Fizz'
      }

      if (entry % 5 === 0) {
        return 'Buzz'
      }

      return entry
    })
    .join(', ')
}
```

Here I implement FizzBuzz when the entry is multiple of 3 *and* a multiple of 5:

```javascript
describe('fizzBuzz', () => {
  it('executes', () => {
    expect(fizzBuzz([1])).toBe('1')
    expect(fizzBuzz([1, 2])).toBe('1, 2')
    expect(fizzBuzz([1, 2, 3])).toBe('1, 2, Fizz')
    expect(fizzBuzz([1, 2, 3, 5])).toBe('1, 2, Fizz, Buzz')
    expect(fizzBuzz([1, 2, 3, 5, 6, 10])).toBe(
      '1, 2, Fizz, Buzz, Fizz, Buzz'
    )
    expect(fizzBuzz([1, 2, 3, 5, 6, 10, 15])).toBe(
      '1, 2, Fizz, Buzz, Fizz, Buzz, FizzBuzz'
    )
  })
})
```

```javascript
export default function fizzBuzz(input) {
  return input
    .map((entry) => {
      if (entry % 3 === 0 && entry % 5 === 0) {
        return 'FizzBuzz'
      }

      if (entry % 3 === 0) {
        return 'Fizz'
      }

      if (entry % 5 === 0) {
        return 'Buzz'
      }

      return entry
    })
    .join(', ')
}
```

This might be a good time to commit the code. Make sure there are no lint warnings/errors and the test is green beforehand! You can run `npm run precommit` if you are following along with the source code.

### Refactor, green, refactor, ..., green

First I remove some duplication:

```javascript
export default function fizzBuzz(input) {
  return input
    .map((entry) => {
      const multipleOf3 = entry % 3 === 0
      const multipleOf5 = entry % 5 === 0

      if (multipleOf3 && multipleOf5) {
        return 'FizzBuzz'
      }

      if (multipleOf3) {
        return 'Fizz'
      }

      if (multipleOf5) {
        return 'Buzz'
      }

      return entry
    })
    .join(', ')
}
```

Make sure the test is still green!

Finally, I decide to extract `processEntry` into a separate function:

```javascript
function processEntry(entry) {
  const multipleOf3 = entry % 3 === 0
  const multipleOf5 = entry % 5 === 0

  if (multipleOf3 && multipleOf5) {
    return 'FizzBuzz'
  }

  if (multipleOf3) {
    return 'Fizz'
  }

  if (multipleOf5) {
    return 'Buzz'
  }

  return entry
}

export default function fizzBuzz(input) {
  return input.map(processEntry).join(', ')
}
```

At this point, I tend to prefer to amend the previous commit with `git commit --amend`. Make sure there are no lint warnings/errors and the test is green beforehand (with `npm run precommit`)!

## Final Thoughts

That's the end of the exercise. I hope you enjoyed it and were able to learn something new. The most important take-away from this exercise is to take small steps! The complete source code can be found on Github in [Java](https://github.com/DeveloperDavo/fizzBuzz) or [JavaScript](https://github.com/DeveloperDavo/fizz-buzz-js).

---

**Before you go…** Thank you for reading this far! If you enjoyed the article, please don't forget to ❤️ it.
 
I write about my professional and educational experiences as a self-taught software developer, so click the **+FOLLOW** button if this interests you! You can also check out [my website](https://learnitmyway.com/) or subscribe to [my newsletter](https://learnitmyway.com/newsletter) for more content.

You might also like:
- [Learn JavaScript with these resources](https://learnitmyway.com/learn-javascript-with-these-resources/)
- [Testing Apollo Server with Typescript](https://learnitmyway.com/apollo-server-testing/)
- [Learning material - software development](https://www.learnitmyway.com/2016/11/11/learning-material-software-development/) (starting with Intro to CS)