Object Mother паттерн

Object Mother — это паттерн проектирования, который используется в тестировании для создания объектов с заранее определёнными значениями. Этот паттерн помогает сделать тесты более читаемыми и удобными, исключая необходимость повторного создания объектов с одинаковыми характеристиками. В контексте тестирования с Jasmine, Object Mother помогает создавать и инициализировать объекты для тестов с минимальными усилиями, а также способствует лучшему поддержанию тестов в будущем.

Зачем использовать Object Mother в тестах?

В процессе написания юнит-тестов часто возникает необходимость создания объектов с одними и теми же значениями для разных тестов. Эти объекты могут быть довольно сложными и требовать многократного повторения инициализации. В таких случаях создание специального класса или функции — “матери” объектов — позволяет вынести логику создания объектов в одно место. Это улучшает читаемость и облегчает поддержание тестов.

Преимущества использования Object Mother:

  • Повторное использование: Создание объектов с одинаковыми характеристиками в одном месте, что позволяет не дублировать код в тестах.
  • Упрощение тестов: Тесты становятся более ясными, так как объекты создаются заранее и не требуют подробной инициализации внутри каждого теста.
  • Гибкость: Легкость в изменении значений по умолчанию без необходимости изменять все тесты.

Как реализовать Object Mother в Jasmine?

Для реализации Object Mother паттерна в Jasmine, необходимо создать утилитный объект или класс, который будет генерировать заранее настроенные объекты для использования в тестах.

Пример реализации:

// Файл: userMother.js

class UserMother {
  static createDefaultUser() {
    return {
      name: 'John Doe',
      email: 'john.doe@example.com',
      age: 30
    };
  }

  static createUserWithName(name) {
    return {
      name: name,
      email: 'default@example.com',
      age: 25
    };
  }

  static createUserWithAge(age) {
    return {
      name: 'Default User',
      email: 'default@example.com',
      age: age
    };
  }
}

export default UserMother;

В этом примере класс UserMother предоставляет несколько статических методов, которые создают пользователей с различными аттрибутами. Метод createDefaultUser создаёт пользователя с предустановленными значениями, тогда как другие методы позволяют изменять некоторые параметры объекта, такие как имя или возраст.

В тестах такие объекты будут использоваться для обеспечения чистоты и наглядности тестов:

import UserMother from './userMother';

describe('User validation tests', () => {
  it('should validate default user', () => {
    const user = UserMother.createDefaultUser();
    expect(user.name).toBe('John Doe');
    expect(user.email).toBe('john.doe@example.com');
    expect(user.age).toBe(30);
  });

  it('should validate user with custom name', () => {
    const user = UserMother.createUserWithName('Jane Doe');
    expect(user.name).toBe('Jane Doe');
    expect(user.email).toBe('default@example.com');
    expect(user.age).toBe(25);
  });

  it('should validate user with custom age', () => {
    const user = UserMother.createUserWithAge(40);
    expect(user.name).toBe('Default User');
    expect(user.email).toBe('default@example.com');
    expect(user.age).toBe(40);
  });
});

Каждый из этих тестов использует метод из UserMother, что делает код более читаемым и удобным для поддержания.

Преимущества использования Object Mother в Jasmine

  1. Снижение дублирования кода: Вместо того чтобы многократно писать код для создания объектов с одинаковыми значениями, можно централизованно управлять их созданием.
  2. Облегчение изменений: Если в будущем потребуется изменить структуру объекта или его поля, достаточно изменить это в одном месте, а не во всех тестах.
  3. Упрощение тестов: Тесты фокусируются на логике, а не на создании объектов. Это делает их более понятными и чистыми.
  4. Гибкость: Паттерн позволяет легко изменять параметры объектов, создавая их с различными данными в зависимости от тестируемого случая.

Когда не стоит использовать Object Mother?

Хотя Object Mother паттерн помогает в большинстве случаев, есть ситуации, когда его применение может быть нецелесообразным. В частности, если структура объектов меняется очень часто или если объекты являются простыми (например, состоящими из одной-двух переменных), использование такого паттерна может избыточным. В таких случаях достаточно использовать базовую инициализацию объектов непосредственно в тестах.

Расширенные примеры

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

Пример:

// Файл: orderMother.js

class OrderMother {
  static createOrderWithUser(user) {
    return {
      orderId: 'ORD123',
      user: user,
      items: [{ name: 'Laptop', price: 1200 }],
      status: 'pending'
    };
  }

  static createOrderWithStatus(status) {
    return {
      orderId: 'ORD123',
      user: { name: 'John Doe', email: 'john.doe@example.com' },
      items: [{ name: 'Laptop', price: 1200 }],
      status: status
    };
  }
}

export default OrderMother;

В этом примере метод createOrderWithUser принимает объект пользователя, который может быть создан через UserMother, и генерирует заказ с этим пользователем. Метод createOrderWithStatus позволяет указать статус заказа. Использование таких методов в тестах делает их более гибкими:

import OrderMother from './orderMother';
import UserMother from './userMother';

describe('Order tests', () => {
  it('should create order with user', () => {
    const user = UserMother.createDefaultUser();
    const order = OrderMother.createOrderWithUser(user);
    expect(order.user.name).toBe('John Doe');
    expect(order.items[0].name).toBe('Laptop');
    expect(order.status).toBe('pending');
  });

  it('should create order with custom status', () => {
    const order = OrderMother.createOrderWithStatus('shipped');
    expect(order.status).toBe('shipped');
  });
});

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

Заключение

Паттерн Object Mother предоставляет удобный способ создания объектов для тестирования в Jasmine. Он способствует улучшению читаемости тестов, снижает дублирование кода и упрощает поддержку тестов в долгосрочной перспективе. Правильное использование этого паттерна помогает сделать тесты более структурированными и предсказуемыми, что особенно важно при сложных тестах с множеством зависимостей и параметров.