Тестирование ошибок и edge cases
Тестирование ошибок и пограничных случаев — проверка поведения кода при невалидных входных данных, исключительных ситуациях и граничных значениях, когда система должна либо корректно обработать проблему, либо выбросить понятную ошибку.
Зачем нужно
Большинство багов в production возникает не в «happy path», а в исключительных ситуациях: пустой массив, null вместо объекта, сетевой сбой, невалидный JSON. Тестирование edge cases делает систему устойчивой к реальным условиям эксплуатации.
Где используется
- Валидация и парсинг пользовательского ввода
- Обработка сетевых ошибок и таймаутов
- Граничные значения в алгоритмах (пустой массив, один элемент)
- TypeScript — проверка путей с
null/undefined
Основной контент
Тестирование выброса ошибок
// synchronous error
test('выбрасывает TypeError при null', () => {
expect( => processUser(null)).toThrow(TypeError);
expect( => processUser(null)).toThrow('User cannot be null');
});
// async error
test('выбрасывает ошибку при 404', async () => {
await expect(fetchUser(999)).rejects.toThrow('Not found');
await expect(fetchUser(999)).rejects.toBeInstanceOf(NotFoundError);
});
Edge cases — чеклист
// Для числовых функций
test('работает с нулём', () => expect(factorial(0)).toBe(1));
test('работает с отрицательным числом', () => {
expect( => factorial(-1)).toThrow;
});
test('работает с очень большим числом', () => {
expect(typeof factorial(20)).toBe('number');
});
// Для строковых функций
test('пустая строка', () => expect(capitalize('')).toBe(''));
test('строка из пробелов', () => expect(capitalize(' ')).toBe(' '));
test('Unicode строка', () => expect(capitalize('привет')).toBe('Привет'));
test('строка с числами', () => expect(capitalize('123abc')).toBe('123abc'));
// Для функций с массивами
test('пустой массив', () => expect(sum()).toBe(0));
test('один элемент', () => expect(sum([5])).toBe(5));
test('массив с NaN', () => expect(isNaN(sum([1, NaN]))).toBe(true));
// Для функций с объектами
test('null — выбрасывает ошибку', () => {
expect( => getProperty(null, 'key')).toThrow;
});
test('undefined key — возвращает undefined', () => {
expect(getProperty({}, 'nonexistent')).toBeUndefined();
});
Тестирование с toThrow
// Проверить что выбрасывается — разные варианты
expect( => fn).toThrow; // любая ошибка
expect( => fn).toThrow(Error); // тип ошибки
expect( => fn).toThrow('message'); // подстрока сообщения
expect( => fn).toThrow(/regex/); // регулярное выражение
expect( => fn).toThrowError(ValidationError); // кастомный класс ошибки
Тестирование сетевых ошибок
jest.mock('./api');
import { fetchData } from './api';
test('обрабатывает сетевую ошибку', async () => {
fetchData.mockRejectedValue(new Error('Network Error'));
render(<DataComponent />);
await screen.findByText('Ошибка загрузки. Попробуйте позже.');
expect(screen.queryByText('Загрузка...')).not.toBeInTheDocument;
});
test('обрабатывает таймаут', async () => {
fetchData.mockRejectedValue(new Error('Request timeout'));
const result = await fetchWithRetry('/api/data', { retries: 3 });
expect(fetchData).toHaveBeenCalledTimes(3); // 3 попытки
expect(result).toBeNull();
});
expect.assertions — защита от "молчащих" тестов
test('async ошибка обязательно проверяется', async () => {
expect.assertions(1); // тест упадёт если expect не вызван ни разу
try {
await fetchUser(null);
} catch (error) {
expect(error.message).toBe('Invalid user id');
}
});
Частые ошибки
- Нет теста на null/undefined — это самый частый источник runtime ошибок; всегда проверяй что происходит при null входе
toThrowдля async без await —expect(asyncFn).toThrowне работает для Promise; используйrejects.toThrow- Тест ловит ошибку но не проверяет —
try { ... } catch (e) {}безexpectделает тест бессмысленным - Нет теста на граничные типы —
NaN,Infinity,-0,''часто вызывают неожиданное поведение
Связанные темы
- _MOC Тестирование
- Тест-дизайн -- граничные значения
- Тест-дизайн -- эквивалентные классы
- Jest -- тестирование асинхронного кода
- Unit тесты