Теперь продумаем ситуацию, когда пользователь пытается сходить в уже занятую клетку.
Такую ситуацию можно обыграть по‑разному, но мы будем в таких случаях выбрасывать исключения.
Чтобы протестировать такой ход, нам нужно занять какую‑то клетку, а затем сделать ход в неё ещё раз.
Подготовку к проверке мы делаем именно в этом тесте. Нельзя где‑то в начале создать новую игру, в первом тесте её изменить, а во втором использовать эти изменения, как начальное состояние.
Каждый тест должен быть независим от других. В правильно структурированном наборе тестов ничего не сломается, если какой‑то тест выкинуть или поменять местами с другими.
test/game.jstest('Throws an exception if user moves in taken cell', () => { const x = 2, y = 2 game.acceptUserMove(x, y) const func = game.acceptUserMove.bind(game, x, y) expect(func).toThrow('cell is already taken') })
Чтобы с наименьшими затратами пройти тест, нам нужно проверить, занята ли клетка. Если она занята, то бросить исключение и закончить работу метода.
Запускаем тесты, проверяем, что ничего не сломалось. Начинаем рефакторить.
src/Game.jsacceptUserMove(x, y) { + if (this._board[x][y]) { + throw new Error('cell is already taken') + return + } + this._updateBoard(x, y) }
Проверка на занятость клетки сейчас слишком прямолинейна и зависит от реализации доски. Вынесем проверку во внутренний метод _isCellFree.
При работе с ошибками нам может понадобиться использовать не стандартный конструктор Error, а собственную реализацию ошибок. Вынесем работу с ошибками тоже в отдельный метод _throwException.
После изменений снова проверяем, что тесты не падают.
src/Game.jsacceptUserMove(x, y) { - if (this._board[x][y]) { - throw new Error('cell is already taken') - return - } + if (!this._isCellFree(x, y)) { + return this._throwException('cell is already taken') + } this._updateBoard(x, y) } _updateBoard(x, y) { this._board[x][y] = this._userMoveSymbol } + _isCellFree(x, y) { + return !this._board[x][y] + } + + _throwException(msg) { + throw new Error(msg) + }