philiprehberger-test_factory

Tests Gem Version Last updated

Lightweight test data factory DSL with sequences and traits

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-test_factory"

Or install directly:

gem install philiprehberger-test_factory

Usage

require "philiprehberger/test_factory"

# Define a factory
Philiprehberger::TestFactory.define(:user) do
  { name: "Alice", email: "alice@example.com", role: "user" }
end

# Build a single object
user = Philiprehberger::TestFactory.build(:user)
# => { name: "Alice", email: "alice@example.com", role: "user" }

# Build with overrides
admin = Philiprehberger::TestFactory.build(:user, role: "admin")
# => { name: "Alice", email: "alice@example.com", role: "admin" }

Traits

Philiprehberger::TestFactory.trait(:user, :admin) { { role: "admin" } }
Philiprehberger::TestFactory.trait(:user, :inactive) { { active: false } }

admin = Philiprehberger::TestFactory.build(:user, traits: [:admin])
# => { name: "Alice", email: "alice@example.com", role: "admin" }

Sequences

Philiprehberger::TestFactory.sequence(:email) { |n| "user_#{n}@example.com" }

# Access via the registry
email = Philiprehberger::TestFactory.send(:registry).next_in_sequence(:email)
# => "user_1@example.com"

Collections

Generate N objects at once with build_list. Each element goes through the normal build path, so sequences increment once per object and overrides or traits apply identically to every element.

Philiprehberger::TestFactory.sequence(:email) { |n| "user_#{n}@example.com" }
Philiprehberger::TestFactory.define(:user) do
  {
    name: "User",
    email: Philiprehberger::TestFactory.send(:registry).next_in_sequence(:email),
    role: "user"
  }
end

users = Philiprehberger::TestFactory.build_list(:user, 3)
# => [
#      { name: "User", email: "user_1@example.com", role: "user" },
#      { name: "User", email: "user_2@example.com", role: "user" },
#      { name: "User", email: "user_3@example.com", role: "user" }
#    ]

# Apply overrides and traits to every element
Philiprehberger::TestFactory.trait(:user, :admin) { { role: "admin" } }

admins = Philiprehberger::TestFactory.build_list(:user, 2, traits: [:admin], name: "Bob")
# => [
#      { name: "Bob", email: "user_4@example.com", role: "admin" },
#      { name: "Bob", email: "user_5@example.com", role: "admin" }
#    ]

# count == 0 returns an empty array
Philiprehberger::TestFactory.build_list(:user, 0)
# => []

# Negative counts raise ArgumentError
Philiprehberger::TestFactory.build_list(:user, -1)
# => ArgumentError: count must be non-negative, got -1

Callbacks

Philiprehberger::TestFactory.define(:user) do |f|
  f.after_build { |obj| obj[:created_at] = Time.now }
  { name: "Alice", email: "alice@example.com" }
end

Transient Attributes

Philiprehberger::TestFactory.define(:user) do |f|
  f.transient { admin false }
  f.after_build { |obj, transients| obj[:role] = "admin" if transients[:admin] }
  { name: "Alice", role: "user" }
end

user = Philiprehberger::TestFactory.build(:user, admin: true)
# => { name: "Alice", role: "admin" }

Associations

Philiprehberger::TestFactory.define(:user) { { name: "Alice" } }
Philiprehberger::TestFactory.define(:post) do |f|
  f.association :author, factory: :user
  { title: "Hello World" }
end

post = Philiprehberger::TestFactory.build(:post)
# => { title: "Hello World", author: { name: "Alice" } }

Reset

Philiprehberger::TestFactory.reset!

API

Method Description
TestFactory.define(name, &block) Register a factory; block returns a hash of defaults
TestFactory.trait(factory_name, trait_name, &block) Register a trait override for a factory
TestFactory.sequence(name, &block) Register a thread-safe auto-incrementing sequence
TestFactory.build(name, traits:, **overrides) Build a single data hash
TestFactory.build_list(name, count, traits:, **overrides) Build N data hashes
TestFactory.reset! Clear all definitions, traits, and sequences
DefinitionProxy#after_build(&block) Register a callback that runs after building
DefinitionProxy#transient(&block) Declare transient attributes excluded from the result
DefinitionProxy#association(name, factory:) Declare an association to another factory

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT