Функции, которые могут завершиться ошибкой
Функция, которой могут передать некорректные данные, обязана сообщить об этом. Обычный способ — выбросить ошибку: при успехе функция возвращает результат, при неудаче выбрасывает Error с объяснением, что пошло не так.
Тест для такой функции должен проверять оба пути: корректные данные возвращают правильное значение и не приводят к ошибке, а некорректные данные действительно вызывают ошибку — функция, которая молча принимает некорректные данные, это тоже ошибка.
assert.throws(fn) — это проверка для «плохого» пути. Для функции, которая возвращает промис, та же проверка записывается как assert.rejects(fn) и ожидается через await.
«Хороший» путь
Для корректных входных данных ничего дополнительного писать не нужно. Вызовите функцию, затем сравните значение. Если вызов вместо этого выбросит ошибку, она сама завершит тест, и отчёт покажет эту ошибку — проверять её заранее не нужно.
Корректные данные, без ошибки
safeDivide(10, 2) должна вернуть 5 и не должна выбрасывать ошибку. Сначала идёт вызов; неожиданная ошибка завершила бы тест прямо здесь. И только потом сравнивается значение.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function safeDivide(a: number, b: number): number {
if (b === 0) {
throw new RangeError("нельзя делить на ноль")
}
return a / b
}
test("safeDivide(10, 2) is 5", () => {
// Неожиданная ошибка завершит тест прямо здесь.
const got = safeDivide(10, 2)
assert.equal(got, 5, `safeDivide(10, 2) = ${got}; ожидалось 5`)
})
«Плохой» путь
Вторая половина контракта: некорректные данные должны приводить к ошибке. assert.throws принимает небольшую функцию, которая выполняет вызов, запускает её и считает тест неуспешным, если ошибка не была выброшена.
Можно также указать, какая именно ошибка должна быть выброшена. Передача класса ошибки вторым аргументом делает тест неуспешным, если выброшена ошибка другого вида.
Некорректные данные должны вызывать ошибку
Деление на ноль обязано завершиться ошибкой. assert.throws вызывает safeDivide(10, 0) и требует RangeError. Если бы функция вместо этого вернула число, тест был бы неуспешным — она приняла данные, которые должна была отвергнуть.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function safeDivide(a: number, b: number): number {
if (b === 0) {
throw new RangeError("нельзя делить на ноль")
}
return a / b
}
test("safeDivide(10, 0) throws", () => {
assert.throws(() => safeDivide(10, 0), RangeError, "ожидалась ошибка при делении на ноль")
})
Таблица с wantErr
Оба пути помещаются в один табличный тест: добавьте каждому случаю поле wantErr, которое говорит, ожидается ли ошибка. Поле want необязательное, потому что у случая с ошибкой нет значения для сравнения.
Каждый подтест сначала смотрит на wantErr. Ожидаемые ошибки проверяются через assert.throws и на этом заканчиваются; остальные случаи сравнивают значение как обычно.
Хорошие и плохие случаи в одной таблице
Четыре случая: три обычных деления и одно деление на ноль с wantErr: true. Один цикл покрывает весь контракт функции, и все четыре подтеста проходят успешно.
Вывод: программа на TypeScript и захваченный вывод
import { test } from "node:test"
import assert from "node:assert/strict"
function safeDivide(a: number, b: number): number {
if (b === 0) {
throw new RangeError("нельзя делить на ноль")
}
return a / b
}
type Case = {
name: string
a: number
b: number
want?: number
wantErr: boolean
}
const cases: Case[] = [
{ name: "simple division", a: 10, b: 2, want: 5, wantErr: false },
{ name: "negative result", a: -9, b: 3, want: -3, wantErr: false },
{ name: "divide zero", a: 0, b: 5, want: 0, wantErr: false },
{ name: "divide by zero", a: 10, b: 0, wantErr: true }
]
test("safeDivide", async (t) => {
for (const c of cases) {
await t.test(c.name, () => {
if (c.wantErr) {
assert.throws(() => safeDivide(c.a, c.b), RangeError)
return
}
const got = safeDivide(c.a, c.b)
assert.equal(got, c.want, `safeDivide(${c.a}, ${c.b}) = ${got}; ожидалось ${c.want}`)
})
}
})