Project: Battleship


It’s time to really flex your muscles. Test Driven Development can certainly feel uncomfortable at first, but becomes more natural with practice. We’re going to implement the classic game ‘Battleship’. If you’ve never played it, or need a refresher you can read about it here and you can play an online version here.

Since we’re doing TDD, it’s important that you don’t get overwhelmed. Simply take it one step at a time. Write a test, then make it pass.

We have not yet discussed testing the appearance of a webpage. Doing this requires a separate set of tools, and it is outside the scope of this unit. For this assignment do your best to isolate every bit of application functionality from the actual DOM manipulation bits. You can use mocks to make sure that DOM methods like appendChild are being called, but try your best to keep those things outside of the app logic.


  1. Begin your app by creating the Ship factory function.
    1. Your ‘ships’ will be objects that include their length, where they’ve been hit and whether or not they’ve been sunk.
    2. REMEMBER you only have to test your object’s public interface. Only methods or properties that are used outside of your ‘ship’ object need unit tests.
    3. Ships should have a hit() function that takes a number and then marks that position as ‘hit’.
    4. isSunk() should be a function that calculates it based on their length and whether all of their positions are ‘hit’.
  2. Create Gameboard factory.
    1. Note that we have not yet created any User Interface. We should know our code is coming together by running the tests. You shouldn’t be relying on console.logs or DOM methods to make sure your code is doing what you expect it to.
    2. Gameboards should be able to place ships at specific coordinates by calling the ship factory function
    3. Gameboards should have a receiveAttack function that takes a pair of coordinates, determines whether or not the attack hit a ship and then sends the ‘hit’ function to the correct ship, or records the coordinates of the missed shot.
    4. Gameboards should keep track of missed attacks so they can display them properly.
    5. Gameboards should be able to report whether or not all of their ships have been sunk.
  3. Create Player.
    1. players can take turns playing the game by attacking the enemy Gameboard.
    2. The game is played against the computer, so make ‘computer’ players capable of making random plays. The AI does not have to be smart, but it should know whether or not a given move is legal. (i.e. it shouldn’t shoot the same coordinate twice).
  4. Create the main game loop and a module for DOM interaction.
    1. At this point it is appropriate to begin crafting your User Interface.
    2. The game loop should set up a new game by creating Players and Gameboards. For now just populate each Gameboard with predetermined coordinates. You can implement a system for allowing players to place their ships later.
    3. We’ll leave the HTML implementation up to you for now, but you should display both the player’s boards and render them using information from the Gameboard class.
      1. You need methods to render the gameboards and to take user input for attacking. For attacks, let the user click on a coordinate in the enemy Gameboard.
    4. The game loop should step through the game turn by turn using only methods from other objects. If at any point you are tempted to write a new function inside the game loop, step back and figure out which class or module that function should belong to.
    5. Create conditions so that the game ends once one players ships have all been sunk. This function is appropriate for the Game module.
  5. Finish it up
    1. There are several options available for letting users place their ships. You can let them type coordinates for each ship, or investigate implementing drag and drop.
    2. You can polish the Intelligence of the computer player by having it try adjacent slots after getting a ‘hit’.
    3. Optionally, create a 2 player option that lets users take turns by passing the device back and forth. If you’re going to go this route, make sure the game is playable on a mobile screen and implement a ‘pass device’ screen so that players don’t see each others boards!

When you finish, create a new branch in the forked exercises repository and submit the Pull Request in the Quiz below.

Material based on Erik Trautman | The Odin Project


  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

More Testing


An important basic concept in testing is isolation. You should only test one method at a time, and your tests for one function should not depend upon an external function behaving correctly – especially if that function is being tested elsewhere. The main reason for this is that when your tests fail, you want to be able to narrow down the cause of this failure as quickly as possible. If you have a test that depends on several functions, it can be hard to tell exactly what is going wrong.

Pure Functions

There are many benefits to using Test Driven Debelopment (TDD) when you write your code. One of the biggest benefits is less obvious at first – it helps you to write better code. If you look back at some of your early projects you will probably notice how tightly coupled everything is. All of your functions include references to functions in other parts of your code, and the whole thing is filled with DOM methods or console.log().

Tightly coupled code is hard to test! Imagine trying to write tests for a function like this:

function guessingGame() {
  const magicNumber = 22;
  const guess = prompt('guess a number between 1 and 100!');
  if (guess > magicNumber) {
    alert('YOUR GUESS IS TOO BIG');
  } else if (guess < magicNumber) {
  } else if (guess == magicNumber) {
    alert('YOU DID IT! 🎉');

Making this testable requires us to split up all the different things that are happening. First, we do not need to test the functions prompt and alert because they are built in to the browser. They are external to our program and whoever wrote them has already tested them. What we do need to test is the number logic, which is much easier if we untangle it from the other functions:

function evaluateGuess(magicNumber, guess) {
  if (guess > magicNumber) {
    return 'YOUR GUESS IS TOO BIG';
  } else if (guess < magicNumber) {
  } else if (guess == magicNumber) {
    return 'YOU DID IT! 🎉';

function guessingGame() {
  const magicNumber = 22;
  const guess = prompt('guess a number between 1 and 100!');
  const message = evaluateGuess(magicNumber, guess);


In this example, the only thing we really need to test is the evaluateGuess function, which is much easier to test because it has a clear input and output and doesn’t call any external functions. This implementation is much nicer as well because it’s much easier to extend. If we wanted to switch out the prompt and alerts for methods that manipulate the DOM we can do that more simply now and if we want to make our game more advanced by letting the user make multiple guesses, that is also easier.

If we had written this program with TDD it is very likely that it would have looked more like the second example to begin with. Test driven development encourages better program architecture because it encourages you to write Pure Functions.


There are two solutions to the ‘tightly coupled code’ problem. The first, and best option is to simply remove those dependencies from your code as we did above, but that is simply not always possible. The second option is mocking – writing "fake" versions of a function that always behaves exactly how you want. For example, if you’re testing a function that gets information from a DOM input, you really don’t want to have to set up a webpage and dynamically insert something into the input just to run your tests. With a mock function, you could just create a fake version of the input-grabbing function that always returns a specific value and use THAT in your test.


  1. If you haven’t already, watch the mocking videos from this series:

  1. Too much mocking can be a bad thing. It is sometimes necessary, but if you have to set up an elaborate system of mocks to test any bit of your code, that means your code is too tightly coupled. These two articles (one and two) might be a little extreme, but they contain several really good points about program architecture and testing.

  2. Now that you have some practice and context for TDD, this section of the Jest docs will probably make good sense to you.

  3. Jest includes some really handy mocking functions. Read about them in the official docs

  4. And finally, if you wish, you can add Jest to your webpack setup. Read about that process here.

Material based on Erik Trautman | The Odin Project


  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

UPDATED: 01.02.2021

Project: Testing Practice


Let’s practice! This testing thing really is not that difficult, but it is quite new. The only way to get comfortable with it is to spend some time doing it.


Write tests for the following functions, and then make the tests pass!

  1. capitalize(string) takes a string and returns that string with the first character capitalized.

  2. reverseString(string) takes a string and returns it reversed.

  3. A calculator object that contains the basic operations: add, subtract, divide, and multiply.

  4. Caesar Cipher. Read about it on this website and play around with this cool Caesar Cipher Exploration Tool from Khan Academy.

    1. Don’t forget to test wrapping from z to a.
    2. Don’t forget to test keeping the same case.
    3. Don’t forget to test punctuation!
    4. For this one, you may want to split the final function into a few smaller functions. One concept of Testing is that you don’t need to explicitly test every function you write… Just the public ones. So in this case you only need tests for the final caesar() function. If it works as expected you can rest assured that your smaller helper functions are doing what they’re supposed to.
  5. Array Analysis. Write a function that takes an array of numbers and returns an object with the following properties: average, min, max, and length.

    const object = analyze([1,8,3,4,2,6]);
    object == {
      average: 4,
      min: 1,
      max: 8,
      length: 6

Special Note on using ES6 import statements with Jest

By default, the current version of Jest will not recognize ES6 import statements. In order for you to be able to use ES6 modules for this project you may do the following:

  1. Install the @babel/preset-env package
npm i -D @babel/preset-env
  1. Create a .babelrc file in the project’s root with the following lines of code:
  "presets": ["@babel/preset-env"]

This will allow you to use import statements. Note that in the Jest docs a similar instruction is laid out here

Additional Resources

Material based on Erik Trautman | The Odin Project


  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

UPDATED: 20.01.2021

Testing Basics


Test Driven Development is a big deal in the modern development landscape. This is a concept that we introduced way back in our Fundamentals section with our JavaScript Exercises. The main idea is simply that you start working on your code by writing automated tests before writing the code that is being tested. There are tons of benefits to working like this, all of which will be discussed in the resources below.

There are many test-running systems available in JavaScript: Mocha, Jasmine, Tape and Jest to name a few. Fortunately the syntax for each one is very similar. They all have their own set of special features, but the basic syntax is almost identical, so in the end it doesn’t matter which one you use. In fact, simply picking which library to use for this curriculum has been quite tricky!

This lesson is going to center around Jest. The biggest reasons for this decision are that one of the best resources we’ve found for explaining JavaScript testing uses it and they have fantastic documentation. In the end, writing tests is less about the syntax and more about the TDD philosophy. The most important issues are knowing why we write tests and what we test rather than how.


  1. Read this short article that outlines the basic process and the benefits of TDD.

  2. Watch at least the first 3 videos of this video series about testing in JavaScript. The first video focuses heavily on the WHY, while the next two go into more depth about the process. Later videos in the series are definitely worthwhile, but the first 3 are enough to get you up and running.

  3. Create a new folder, be careful the name you give it should not contain any spaces (e.g no: "Jest App", yes: "JestApp"). Open Visual Studio Code and navigate to that folder. Open the terminal and type npm init --yes to initialize a default package.json file. Then read and follow the Getting Started tutorial on the main Jest website.

  4. Read and follow the Using Matchers document on the main Jest website. This one demonstrates some of the other useful functions you can use in your tests.

  5. Watch this amazing next video that covers what to test in your codebase. The video is specifically about testing the Ruby language, but that doesn’t matter at all. The concepts here ring true in any language, and luckily Ruby is a clear enough language that you will be able to follow along just fine. This video might be worth re-visiting after you’ve done some testing of your own.

Material based on Erik Trautman | The Odin Project


  • Is there anything we can help with up to this point? Do you have something to suggest about this chapter? Let us know in the comments below.

UPDATED: 08.03.2021