..

Создание не Active Record сущностей через FactoryBot

31 Jan 2025

Почти все rails-разработчики знакомы с FactoryBot и с ее возможностями для создания тестовых данных. Однако, я у себя в проекте не использую AR, у меня репозитории и Entities, которые полностью in-memory объекты. Так называемые Plain Old Ruby Object (PORO). Что позволяет делать FactoryBot с такими классами?

На самом деле, много чего.

Объявление фабрики для своего кастомного класса:

factory :file, class: "Domain::File" do
  author factory: %i[user]
  name { "file.txt" }
  id { Random.uuid }
end

После чего в тесте можно использовать ее так же, как и другие фабрики:

let(:file) { build(:file) }

Кроме этого, можно настроить логику инициализации объекта, если конструктор класса принимает не один объект со всеми параметрами attrs={}, как это делает AR. Например, у нас принято использовать именованные аргументы:

 initialize_with do
   new(
     author:,
     name:,
     id:
   )
 end

Но все это позволяет в тестах использовать только build(:file). Понятно, что мы для того и отделяли бизнес-логику от логики хранения, чтобы тестировать их в отвязке от БД. Но иногда нужно создавать именно сохраненные объекты.

FactoryBot предоставляет возможность объявить механизм, который позволит использовать create(:file) . Например, вот так можно сделать сохранение в репозиторий.

to_create do |file, evaluator|
  DependenciesContainer.resolve(:files).save(file)
end

После этого начнет работать create(:file), который вернет созданный объект:

let(:file) { create(:file) }

it { expect(files.exists?(file.id)).not_to be_nil }

Кстати, такое создание не всегда означает создание записи в базе данных. Мы активно используем fake-репозитории в тестах, которые мы подключаем через dry-auto_inject. Таким образом, если в тесте подключены FakeFiles вместо реального репозитория, тогда такая фабрика будет работать тоже in-memory.

Ну и в завершении, весть пример в сборе

FactoryBot.define do
factory :file, class: "Domain::File" do
  author factory: %i[user]
  name { "file.txt" }
  id { Random.uuid }

  initialize_with do
    new(
      author:,
      name:,
      id:
    )
  end
  
  to_create do |file, evaluator|
    DependenciesContainer.resolve(:files).save(file)
  end
end

FactoryBot - это не просто фабрика для ваших ActiveRecord моделей. Это мощный инструмент с огромными возможностями для создания любых объектов в ваших тестах.