Мокирование ES6 классов

При тестировании JavaScript-программ с использованием Jest часто возникает необходимость имитировать поведение классов или их методов. Мокирование классов позволяет изолировать тестируемый код от внешних зависимостей, делая тесты быстрее и стабильнее. В этой главе рассматриваются основные методы мокирования ES6 классов с использованием Jest.

Зачем нужно мокирование классов?

Мокирование классов необходимо для тестирования компонентов или модулей, которые зависят от сложных или внешних объектов, например, API, баз данных, файловых систем или сторонних библиотек. В случае с ES6 классами, мокирование помогает:

  • Избежать реальных вызовов в тестах, которые могут быть медленными или иметь побочные эффекты.
  • Легко контролировать поведение классов и их методов.
  • Упростить тестирование частей кода, которые не могут быть напрямую протестированы из-за их зависимости от внешних классов или сервисов.

Основы мокирования классов

Jest предоставляет несколько способов мокирования классов, включая использование функций jest.mock() и jest.fn(). Для начала рассмотрим, как можно замокировать простой ES6 класс.

class MyClass {
  method1() {
    return "real value";
  }

  method2() {
    return "real value 2";
  }
}

// Замокировать класс MyClass
jest.mock('./MyClass', () => {
  return jest.fn().mockImplementation(() => {
    return {
      method1: jest.fn().mockReturnValue('mocked value'),
      method2: jest.fn().mockReturnValue('mocked value 2')
    };
  });
});

В этом примере класс MyClass мокируется с помощью jest.mock(), а его методы заменяются на мок-версии с заранее заданными значениями.

Мокирование с сохранением оригинальной реализации

Иногда полезно замокировать только некоторые методы класса, оставив остальные в их оригинальном виде. Для этого можно использовать частичное мокирование.

jest.mock('./MyClass', () => {
  const originalClass = jest.requireActual('./MyClass');
  return {
    ...originalClass,
    method1: jest.fn().mockReturnValue('mocked value')
  };
});

В этом примере метод method1 заменяется на замокированную версию, а метод method2 сохраняет свою оригинальную реализацию.

Использование классов с мокированием конструктора

Для мокирования конструктора класса, который создается с помощью new, также можно использовать Jest. Важно понимать, что jest.fn() и jest.mock() работают не только с функциями, но и с конструкторами классов.

class MyClass {
  constructor() {
    this.value = 42;
  }

  method() {
    return this.value;
  }
}

jest.mock('./MyClass', () => {
  return jest.fn().mockImplementation(() => {
    return { method: jest.fn().mockReturnValue('mocked value') };
  });
});

const instance = new MyClass();
console.log(instance.method()); // Выведет "mocked value"

Здесь MyClass замокирован так, чтобы при создании экземпляра возвращался объект с замокированным методом method.

Мокирование асинхронных методов класса

Многие методы классов в реальных приложениях асинхронны. Для мокирования асинхронных методов используется jest.fn() с асинхронными колбэками.

class MyClass {
  async fetchData() {
    return 'real data';
  }
}

jest.mock('./MyClass', () => {
  return jest.fn().mockImplementation(() => {
    return { fetchData: jest.fn().mockResolvedValue('mocked data') };
  });
});

const instance = new MyClass();
instance.fetchData().then(result => console.log(result)); // "mocked data"

В данном примере метод fetchData замокирован с использованием mockResolvedValue(), что позволяет вернуть промис с заданным значением. Также можно использовать mockRejectedValue() для эмуляции ошибок.

Проверка вызова мокированных методов

После мокирования классов и их методов часто требуется убедиться, что методы были вызваны, а также какие аргументы были переданы. Jest предоставляет удобные методы для таких проверок.

const mockMethod = jest.fn();
class MyClass {
  method1(arg) {
    mockMethod(arg);
  }
}

const instance = new MyClass();
instance.method1('test');

expect(mockMethod).toHaveBeenCalledWith('test');
expect(mockMethod).toHaveBeenCalledTimes(1);

В этом примере проверяется, что метод method1 был вызван с аргументом 'test' и вызван один раз.

Важные моменты при мокировании классов

  • Перезапуск моков: Jest автоматически сбрасывает мокированные данные между тестами, но если необходимо сбросить мокированные методы вручную, можно использовать jest.clearAllMocks() или jest.resetAllMocks().

  • Мокирование зависимостей классов: В случае, если один класс зависит от другого, например, при создании экземпляра одного класса в другом, можно замокировать зависимость через jest.mock().

  • Мокирование статических методов: Статические методы классов можно замокировать аналогично обычным методам, с использованием jest.fn() и других вспомогательных функций.

class MyClass {
  static staticMethod() {
    return 'real value';
  }
}

jest.mock('./MyClass', () => {
  return {
    ...jest.requireActual('./MyClass'),
    staticMethod: jest.fn().mockReturnValue('mocked value')
  };
});

Пример комплексного мокирования

Рассмотрим более сложный пример, где классы имеют зависимости от других классов и взаимодействуют с внешними ресурсами. В этом случае можно замокировать все внешние зависимости, чтобы тестировать только основную логику.

class Database {
  save(data) {
    // Реальная логика сохранения данных
    return 'data saved';
  }
}

class Service {
  constructor(db) {
    this.db = db;
  }

  async saveData(data) {
    return await this.db.save(data);
  }
}

jest.mock('./Database', () => {
  return jest.fn().mockImplementation(() => {
    return { save: jest.fn().mockResolvedValue('mocked data saved') };
  });
});

const db = new Database();
const service = new Service(db);

service.saveData('test').then(result => console.log(result)); // "mocked data saved"

Здесь класс Database мокируется, чтобы метод save возвращал замокированное значение. Это позволяет протестировать класс Service, не затрагивая реальную логику работы с базой данных.

Заключение

Мокирование ES6 классов в Jest — мощный инструмент для создания изолированных и стабильных тестов. Оно позволяет легко контролировать поведение классов и их методов, имитировать асинхронные операции, а также проверять, как методы и конструкторы были вызваны в процессе тестирования.