Javascript тестирование – практика UNIT тестирование

О теории (зачем нужно тестирование и каким бывает) написано здесь.

1. Подготовка приложения для UNIT тестов

  1. Проинициализируем приложение при помощи npm init
  2. Устанавливаем сам jest npm i -D jest (как дев зависимость, потому и параметр -D)
  3. В package.json файле заменим команду для тестирования на “jest”

Установка и настройка jest

  1. Создаем файлик validateValue.js (для функций) и validateValue.test.js (для тестов к ним).

В первом файле создаем функцию для тестирования и импортируем ее во второй, где она будет тестироваться.

2. Самое простое тестирование (синхронное)

Основные функции:

Функция test() первым аргументом принимает название теста, а вторым – колбек, который и делает проверку.

Функция expect – главная единица в юнит-тестах, куда передается результат теста и потом вызывается один из множества методов, таких как “чему то равен”, “больше чем что-то” и т.д. В примере выше мы проверяем, что функция вернула true либо false, используя метод toBe().

Функция describe, как видно выше, просто оборачивает тесты в отдельные группы (наборы тестов). Это, грубо говоря, такая обертка над тестами. А функция test выполняет уже непосредственно тест. Можно было бы и не оборачивать test в describe, это нужно просто для более удобного чтения результатов.

1) Пример с методом toBe() (для сравнения простых типов)

Метод toBe у expect просто сравнивает полученное значение с ожидаемым. Он подходит для сравнения простых типов и не подойдет для сравнения объектов, так как будет сравнивать ссылки.

Функция, котору тестируем:

Функция для тестирования
Функция для тестирования в validateValue.js

Пример тестов:

const validateValue = require("./validateValue");

describe("Набор тестов", () => {
  test("Валидное значение", () => {
    expect(validateValue(50)).toBe(true);
  });

  test("Макс", () => {
    expect(validateValue(100)).toBe(true);
  });

  test("Мин", () => {
    expect(validateValue(0)).toBe(true);
  });

  test("Меньше", () => {
    expect(validateValue(-1)).toBe(false);
  });

  test("Больше", () => {
    expect(validateValue(101)).toBe(false);
  });
});

Чтобы понять, почему именно такой набор тестов, надо вспомнить, что такое “тестовый квадрат“. Согласно его прнцыпу, мы берем значение “из центра”, “пограничны” и “невалидные”

Запускаем npm run test для всех тестов или для конкретного файла npm run test validateValue.test.js видим в консоли IDE результат:

Результат jest тестов
Результат простых unit тестов, выполненных jest

Обратите внимание, что jest ищет тесты во всех файлах, которые в названии содержат слово “test”.

2) Пример с методом toEqual или toStrictEqual (для сравнения объектов / массивов)

Эти методы использует рекурсивное сравнение, поэтому применяется для сравнения объектов (сравнивает значения внутри них). toStrictEqual более новый, и в отличие от toEqual еще делает и сравнение типов сравниваемых значений.

Функция, котору тестируем:

Простая функция, которая числа в массиве преобразовывает в строки.

Функция для тестирования 2

Пример тестов:

const mapArrToString = require("./mapArrToString");

describe("mapArrToString", () => {
  test("Корректное значение", () => {
    expect(mapArrToString([1, 2, 3])).toEqual(["1", "2", "3"]);
  });

  test("Фильтрование других типов", () => {
    expect(mapArrToString([1, 2, 3, null, undefined, "fsdf"])).toStrictEqual([
      "1",
      "2",
      "3",
    ]);
  });

  test("Нет значений", () => {
    expect(mapArrToString([])).toStrictEqual([]);
  });

  test("Отрицание", () => {
    expect(mapArrToString([1, 2, 3])).not.toStrictEqual(["1", "2", "3", "4"]);
  });
});

Как видно в последнем тесте применяется еще и свойство not, у возвращаемого объекта функцией expect. Это аналог оператора неравенства, обозначающий, что ожидаемое значение не должно соответствовать указанному результату.

3) Примеры других методов, а также функций, которые отрабатывают до и после тестов

Функция, котору тестируем:

Функция, возводящая в квадрат число.

Функция для тестирования 3

Пример тестов:

describe("square", () => {

  beforeEach(() => {
    //вызовется перед каждым тестом
    //например, добавили юзера в БД (потом нужно сразу удалить)
  });

  beforeAll(() => {
    //вызовется один раз перед всеми тестами
  });

  test("Корректное значение", () => {
    expect(square(2)).toBe(4);
    expect(square(2)).toBeLessThan(5);
    expect(square(2)).toBeGreaterThan(3);
    expect(square(2)).not.toBeUndefined();
  });

  afterEach(() => {
    //вызовется после каждого теста
    //например, удалили созданного перед тестом юзера с БД
  });

  afterAll(() => {
    //вызовется один раз после всех тестов
  });
});

4) Примеры с моками метода (spyOn) и очисткой моков (clearAllMocks)

Иногда нам надо замокать методы какой-то сторонней библиотеки, например, axios. Для этого существует метод spyOn(), принимающий первым аргументом модуль, а вторым – метод, который мы хотим замокать.

Потом этот замоканный метод передаем в expect и ожидаем, что этот метод будет вызван. Но для этого нужно выше вызвать саму нашу функцию, чтобы.

Функция, котору тестируем:

Та же самая с возведением в квадрат числа.

Пример тестов:

const square = require("./square");

describe("square", () => {
  test("Корректное значение 1", () => {
    //мокаем метод pow объекта Math, кторый есть в нашей функции
    const spyMathPow = jest.spyOn(Math, "pow");
    square(2); //нужен вызов функции, чтобы протестировать в ней замоканный метод
    expect(spyMathPow).toBeCalledTimes(1);
  });

  test("Корректное значение 2", () => {
    //повторно используем наши моки метода
    const spyMathPow = jest.spyOn(Math, "pow");
    square(1);
    expect(spyMathPow).toBeCalledTimes(0); //если бы не очистка ниже в afterEach, то тест бы упал!!!
  });

  afterEach(() => {
    //после теста моки нужно очищать, иначе они накапливают эти вызовы и послежующий тест отработает неправильно
    jest.clearAllMocks();
  });
});

Как видно с комментариев, моки нужно очищать при помощи clearAllMocks() – метода у глобального объекта jest.

3. Асинхронное тестирование

1) Тестирование примитивной асинхронной функции на промисе

Тестирование простой асинхронной функции, возвращающей Promise, внутри которого лежит setTimeout, мало чем отличается от предыдущих примеров.

Функция, котору тестируем:

Асинхронная функция для тестирования с промисом

Пример тестов:

Колбек в функции test делаем асинхронным, затем результат выполнения нашей тестируемой функции помещаем в переменную sum, а его кладем уже в expect. И все работает.

const delay = require("./delay");

describe("delay", () => {
  test("Корректное значение", async () => {
    const sum = await delay(() => 5 + 5, 1000);
    expect(sum).toBe(10);
  });
});

2) Тестирование функции с реальным запросом через axios

Для этого устанавливаем axiox (npm i axios), импортируем и делаем запрос на /my-json-server. Затем из полученного ответа мапаем массив id, который пропускам через созданную ранее функцию для тестов mapArrToString. Здесь уже получается своего рода намек на интеграционное (с несколькими модулями) тестирование, но по факту это все равно unit тест…

Функция, котору тестируем:

Асинхронная функция для тестирования 2

 

Пример тестов:

1) Импортируем axios и здесь. Теперь нам надо замокать данные, которые будет возвращать метод гет у аксиоса.

Все сторонние или внутренние модули мокаются через функцию mock у джеста: jest.mock("module_name");

2) Создаем переменную response и в beforeEach кладем в нее моки (аналог объекта, приходящего в респосне). Почему нужен beforeEach, говорилось выше – чтобы вызовы не накапливались и не было ошибки.

3) Внутри теста “подменяем” возвращаемое значение аксиоса этими моками при помощи функции  mockReturnValue.

4) Не забываем дописать колбеку теста async, так как функция асинхронная. И получаем результат тестируемой в переменную data, указав await.

5) Указываем, что ожидаем вызова аксисоа хотя бы один раз toBeCalledTimes(1), что результат будет массив со строками toEqual([“1”, “2”, “3”])

6) А также показан вызов функции метода toMatchInlineSnapshot(). Это функция для сохранения результатов вычислений, помогающая потом наглядно увидеть, что изменилось. Но это больше для компонентов, например, возвращаемого jsx (отрисовалась ли кнопочка, наложились ли стили и т.п.). При выполнении запишется возвращаемый результат тестироемой функции (в отдельный файл или прямо здесь), который в будущем при тесте будет сравниваться.

const getData = require("./getData");
const axios = require("axios");

jest.mock("axios");

describe("getData", () => {
  let response;

  beforeEach(() => {
    response = {
      data: [
        {
          id: 1,
          title: "Post 1",
        },
        {
          id: 2,
          title: "Post 2",
        },
        {
          id: 3,
          title: "Post 3",
        },
      ],
    };
  });

  test("Корректное значение", async () => {
    axios.get.mockReturnValue(response);
    const data = await getData();

    expect(axios.get).toBeCalledTimes(1);
    expect(data).toEqual(["1", "2", "3"]);
    expect(data).toMatchInlineSnapshot(); //функция для сохранения результатов вычислений,
    //помогающая потом наглядно увидеть, что изменилось и сравнить пото при тестах последующих.

  });
});

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *