Task 002: FizzBuzz - MrKiplin/learn-typescript GitHub Wiki

Task description

Find all multiples of 3 & 5 within a range of 1000. Return multiples of 3 as Fizz, multiples of 5 as Buzz and multiples of both as FizzBuzz.

Step by step guide

For this task our tests are already written. All the tests will fail at this point and it's our job to write code that will make them pass. This is called TDD or Test Driven Development and is a very disciplined approach to development. The general principle behind this is to write you tests first then write the minimal amount of code to make that test pass. This allows for the writing of very lean and easy to refactor code.

Let's change to the directory of the task ./typescript-tasks/001-fizzbuzz and run the tests. We can do this using the describe block description.

yarn test -t "fizzBuzz"

Alternatively if we want to run a specific test then we can use the test description.

yarn test -t "Should return Fizz if value is multiple of 3"

Let's focus firstly on getting this test to pass. Start by creating a fizzBuzz function and exporting it. While we are at it let's add another function called fizzBuzzLogger for use later. This is to stop the tests complaining about unused imports.

export const fizzBuzz = () => {};

export const fizzBuzzLogger = () => {};

The export before the function allows us to access it in our test file using import { fizzBuzz } from "./001-fizzbuzz". Next we need to add an argument for the function. Let's simply call this value. Because TypeScript uses static typing, we need to also provide a type for our value argument. The most common types you will use are string, number and boolean. TypeScript also allows you to create custom types, classes, interfaces and enums that can be used in all sorts of cool ways but we will get to these in later tasks. For this case we want to pass numbers into the function so let's give the argument a type of number.

export const fizzBuzz = (value: number) => {};

Next let's add logic to check if our provided value is a multiple of 3. Add a variable named isFizz and use the condition value % 3 === 0 to see if the provide value is a multiple of 3. What this is essentially doing is saying divide value by 3 value % 3 and check the remaining amount is 0 === 0.

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
};

We are now able to use this variable to check if the provided value is multiple of 3 and return Fizz if true. We will use a simple if statement to do this.

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
  
  if (isFizz) {
    return "Fizz";
  } 
};

If we now run our test again yarn test -t "Should return Fizz if value is multiple of 3". Congratulation the test now passes.

Let's focus on fixing the next test yarn test -t "Should return Buzz if value is multiple of 5". Same as before let's add variable for checking if the given value is a multiple of 5 and name it isBuzz.

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
  const isBuzz = value % 5 === 0;
  
  if (isFizz) {
    return "Fizz";
  } 
};

Same as before, we are now able to use this variable to check if the value is multiple of 5 and return Buzz. We will do this using an else if statement.

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
  const isBuzz = value % 5 === 0;
  
  if (isFizz) {
    return "Fizz";
  } else if (isBuzz) {
    return "Buzz";
  }
};

If we now run our test again yarn test -t "Should return Buzz if value is multiple of 5". Congratulation the test now passes.

Let's switch our attention to the next test yarn test -t "Should return FizzBuzz if value is multiple of 3 & 5". We already have the logic to check for multiples of 3 and 5 so let's add another if else statement and check if the provided value is multiple of both and if true return FizzBuzz.

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
  const isBuzz = value % 5 === 0;
  
  if (isFizz) {
    return "Fizz";
  } else if (isBuzz) {
    return "Buzz";
  } else if (isFizz && isBuzz) {
    return "FizzBuzz";
  }
};

If we now run our test again yarn test -t "Should return FizzBuzz if value is multiple of 3 & 5". Congratulation the test now passes. At this stage let's run all the tests to check that all three are still passing.

yarn test -t "fizzBuzz"

Great! all three are still passing. This allows us to do a bit of refactoring and have confidence that we've not broken anything. These are the real benefits of coding using TDD. Let's update the function so that the FizzBuzz check is preformed in the initial if statement and the other checks in the subsequent else if statements.

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
  const isBuzz = value % 5 === 0;

  if (isFizz && isBuzz) {
    return "FizzBuzz";
  } else if (isFizz) {
    return "Fizz";
  } else if (isBuzz) {
    return "Buzz";
  }
};

If we now run our tests again yarn test -t "fizzBuzz". Fantastic our tests are still passing.

Last test to focus on for this section is yarn test -t "Should return value if value is not a multiple of 3 or 5". For this we simply want to return the given value if it does not match our custom conditions. To do this add a final else statement and return the value.

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
  const isBuzz = value % 5 === 0;

  if (isFizz && isBuzz) {
    return "FizzBuzz";
  } else if (isFizz) {
    return "Fizz";
  } else if (isBuzz) {
    return "Buzz";
  } else {
    return value;
  }
};

If we now run our test again yarn test -t "Should return value if value is not a multiple of 3 or 5". Congratulation the test now passes.

Let's move on to the last test yarn test -t "Should return an array containing FizzBuzz, Fizz, Buzz & values". Firstly let's add an argument to our fizzBuzzLogger. Because we want to pass this function a range to iterate over, let's call this range and give it a type of number.

export const fizzBuzzLogger = (range: number) => {};

Next step is to set a blank array and return it. We will us this to push the returned values to using our new fizzBuzz function.

export const fizzBuzzLogger = (range: number) => {
  const values = [];

  return values;
};

Next let's iterate through our provided range using a for loop and call our fizzBuzz function for each value in the range. We then want to push the returned value to our values variable.

export const fizzBuzzLogger = (range: number) => {
  const values = [];
  for (let value = 0; value <= range; value++) {
    values.push(fizzBuzz(value));
  }
  return values;
};

Let's run all the tests for the final time.

yarn test -t "fizzBuzz"

Congratulations all the tests are passing and you have completed the first task. Will see you on the next task.

Completed solution

002-fizzbuzz.ts

export const fizzBuzz = (value: number) => {
  const isFizz = value % 3 === 0;
  const isBuzz = value % 5 === 0;

  if (isFizz && isBuzz) {
    return "FizzBuzz";
  } else if (isFizz) {
    return "Fizz";
  } else if (isBuzz) {
    return "Buzz";
  } else {
    return value;
  }
};

export const fizzBuzzLogger = (range: number) => {
  const values = [];
  for (let value = 0; value <= range; value++) {
    values.push(fizzBuzz(value));
  }
  return values;
};

002-fizzbuzz.spec.ts

import { fizzBuzz, fizzBuzzLogger } from "./001-fizzbuzz";

describe("fizzBuzz", () => {
  it("Should return Fizz if value is multiple of 3", () => {
    const result = fizzBuzz(3);
    expect(result).toMatch("Fizz");
  });
  it("Should return Buzz if value is multiple of 5", () => {
    const result = fizzBuzz(5);
    expect(result).toMatch("Buzz");
  });
  it("Should return FizzBuzz if value is multiple of 3 & 5", () => {
    const result = fizzBuzz(0);
    expect(result).toMatch("FizzBuzz");
  });
  it("Should return value if value is not a multiple of 3 or 5", () => {
    const result = fizzBuzz(1);
    expect(result).toEqual(1);
  });
  it("Should return an array containing FizzBuzz, Fizz, Buzz & values", () => {
    const result = fizzBuzzLogger(5);
    expect(result).toContain("Fizz");
    expect(result).toContain("Buzz");
    expect(result).toContain("FizzBuzz");
    expect(result).toContain(1);
  });
});