1
0
Fork 0
mirror of https://github.com/thoughtbot/factory_bot.git synced 2022-11-09 11:43:51 -05:00

Register inline sequence to allow for rewinding

This was originally opened as #1078, but this addresses the review
comments on that PR.

By registering the inline sequences, we allow them to get rewound with
`FactoryBot.rewind_sequences`. We register them with
`__#{factory_name}_#{sequence_name}__` to avoid conflicting with any
reasonably named global sequences, and to hint that we should not be
generating values from these sequences directly.

Co-authored-by: Damian Le Nouaille <dam@dln.name>
Co-authored-by: Damian Galarza <galarza.d@gmail.com>
This commit is contained in:
Daniel Colson 2018-08-04 10:06:57 -04:00
parent 676fa9d3d9
commit f1d7ae3cc1
6 changed files with 185 additions and 81 deletions

View file

@ -11,7 +11,7 @@ module FactoryBot
@traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Trait'))
@strategies = Registry.new('Strategy')
@callback_names = Set.new
@definition = Definition.new
@definition = Definition.new(:configuration)
@allow_class_lookup = true

View file

@ -1,9 +1,10 @@
module FactoryBot
# @api private
class Definition
attr_reader :defined_traits, :declarations
attr_reader :defined_traits, :declarations, :name
def initialize(name = nil, base_traits = [])
def initialize(name, base_traits = [])
@name = name
@declarations = DeclarationList.new(name)
@callbacks = []
@defined_traits = Set.new

View file

@ -121,7 +121,9 @@ module FactoryBot
#
# Except that no globally available sequence will be defined.
def sequence(name, *args, &block)
sequence = Sequence.new(name, *args, &block)
sequence_name = "__#{@definition.name}_#{name}__"
sequence = Sequence.new(sequence_name, *args, &block)
FactoryBot.register_sequence(sequence)
add_attribute(name) { increment_sequence(sequence) }
end

View file

@ -20,4 +20,80 @@ describe "FactoryBot.rewind_sequences" do
expect(email).to eq "somebody1@example.com"
expect(name).to eq "Joe"
end
it "resets inline sequences back to their starting value" do
class User
attr_accessor :email
end
FactoryBot.define do
factory :user do
sequence(:email) { |n| "somebody#{n}@example.com" }
end
end
build_list(:user, 2)
FactoryBot.rewind_sequences
user = build(:user)
expect(user.email).to eq "somebody1@example.com"
end
it "does not collide with globally registered factories" do
class User
attr_accessor :email
end
FactoryBot.define do
sequence(:email) { |n| "global-somebody#{n}@example.com" }
factory :user do
sequence(:email) { |n| "local-somebody#{n}@example.com" }
end
end
2.times do
generate(:email)
end
build_list(:user, 2)
FactoryBot.rewind_sequences
user = build(:user)
email = generate(:email)
expect(user.email).to eq "local-somebody1@example.com"
expect(email).to eq "global-somebody1@example.com"
end
it "still allows global sequences prefixed with a factory name" do
class User
attr_accessor :email
end
FactoryBot.define do
sequence(:user_email) { |n| "global-somebody#{n}@example.com" }
factory :user do
sequence(:email) { |n| "local-somebody#{n}@example.com" }
end
end
2.times do
generate(:user_email)
end
build_list(:user, 2)
FactoryBot.rewind_sequences
user = build(:user)
email = generate(:user_email)
expect(user.email).to eq "local-somebody1@example.com"
expect(email).to eq "global-somebody1@example.com"
end
end

View file

@ -1,5 +1,5 @@
describe FactoryBot::DefinitionProxy, "#add_attribute" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "raises if both a block and value are given" do
@ -21,7 +21,7 @@ describe FactoryBot::DefinitionProxy, "#add_attribute" do
end
describe FactoryBot::DefinitionProxy, "#add_attribute when the proxy ignores attributes" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject, true) }
it "raises if both a block and value are given" do
@ -43,7 +43,7 @@ describe FactoryBot::DefinitionProxy, "#add_attribute when the proxy ignores att
end
describe FactoryBot::DefinitionProxy, "#transient" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "makes all attributes added ignored" do
@ -56,7 +56,7 @@ describe FactoryBot::DefinitionProxy, "#transient" do
end
describe FactoryBot::DefinitionProxy, "#method_missing" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "declares an implicit declaration without args or a block" do
@ -82,30 +82,44 @@ describe FactoryBot::DefinitionProxy, "#method_missing" do
end
describe FactoryBot::DefinitionProxy, "#sequence" do
subject { FactoryBot::Definition.new }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
before { allow(FactoryBot::Sequence).to receive(:new) }
before do
allow(FactoryBot::Sequence).to receive(:new).and_call_original
end
def build_proxy(factory_name)
definition = FactoryBot::Definition.new(factory_name)
FactoryBot::DefinitionProxy.new(definition)
end
it "creates a new sequence starting at 1" do
proxy.sequence(:great)
expect(FactoryBot::Sequence).to have_received(:new).with(:great)
proxy = build_proxy(:factory)
proxy.sequence(:sequence)
expect(FactoryBot::Sequence).to have_received(:new).
with("__factory_sequence__")
end
it "creates a new sequence with an overridden starting vaue" do
proxy.sequence(:great, "C")
expect(FactoryBot::Sequence).to have_received(:new).with(:great, "C")
proxy = build_proxy(:factory)
proxy.sequence(:sequence, "C")
expect(FactoryBot::Sequence).to have_received(:new).
with("__factory_sequence__", "C")
end
it "creates a new sequence with a block" do
sequence_block = Proc.new { |n| "user+#{n}@example.com" }
proxy.sequence(:great, 1, &sequence_block)
expect(FactoryBot::Sequence).to have_received(:new).with(:great, 1, &sequence_block)
proxy = build_proxy(:factory)
proxy.sequence(:sequence, 1, &sequence_block)
expect(FactoryBot::Sequence).to have_received(:new).
with("__factory_sequence__", 1, &sequence_block)
end
end
describe FactoryBot::DefinitionProxy, "#association" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "declares an association" do
@ -120,7 +134,7 @@ describe FactoryBot::DefinitionProxy, "#association" do
end
describe FactoryBot::DefinitionProxy, "adding callbacks" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
let(:callback) { -> { "my awesome callback!" } }
@ -159,7 +173,7 @@ describe FactoryBot::DefinitionProxy, "adding callbacks" do
end
describe FactoryBot::DefinitionProxy, "#to_create" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "accepts a block to run in place of #save!" do
@ -170,7 +184,7 @@ describe FactoryBot::DefinitionProxy, "#to_create" do
end
describe FactoryBot::DefinitionProxy, "#factory" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "without options" do
@ -191,7 +205,7 @@ describe FactoryBot::DefinitionProxy, "#factory" do
end
describe FactoryBot::DefinitionProxy, "#trait" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "declares a trait" do
@ -202,7 +216,7 @@ describe FactoryBot::DefinitionProxy, "#trait" do
end
describe FactoryBot::DefinitionProxy, "#initialize_with" do
subject { FactoryBot::Definition.new }
subject { FactoryBot::Definition.new(:name) }
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
it "defines the constructor on the definition" do

View file

@ -1,64 +1,75 @@
describe FactoryBot::Definition do
subject { described_class.new(:name) }
it { should delegate(:declare_attribute).to(:declarations) }
end
describe FactoryBot::Definition, "with a name" do
let(:name) { :"great name" }
subject { FactoryBot::Definition.new(name) }
describe "with a name" do
it "creates a new attribute list with the name passed" do
name = "great name"
allow(FactoryBot::DeclarationList).to receive(:new)
it "creates a new attribute list with the name passed" do
allow(FactoryBot::DeclarationList).to receive(:new)
subject
expect(FactoryBot::DeclarationList).to have_received(:new).with(name)
end
end
describe FactoryBot::Definition, "#overridable" do
let(:list) { double("declaration list", overridable: true) }
before do
allow(FactoryBot::DeclarationList).to receive(:new).and_return list
end
it "sets the declaration list as overridable" do
expect(subject.overridable).to eq subject
expect(list).to have_received(:overridable).once
end
end
describe FactoryBot::Definition, "defining traits" do
let(:trait_1) { double("trait") }
let(:trait_2) { double("trait") }
it "maintains a list of traits" do
subject.define_trait(trait_1)
subject.define_trait(trait_2)
expect(subject.defined_traits).to include(trait_1, trait_2)
end
it "adds only unique traits" do
subject.define_trait(trait_1)
subject.define_trait(trait_1)
expect(subject.defined_traits.size).to eq 1
end
end
describe FactoryBot::Definition, "adding callbacks" do
let(:callback_1) { "callback1" }
let(:callback_2) { "callback2" }
it "maintains a list of callbacks" do
subject.add_callback(callback_1)
subject.add_callback(callback_2)
expect(subject.callbacks).to eq [callback_1, callback_2]
end
end
describe FactoryBot::Definition, "#to_create" do
its(:to_create) { should be_nil }
it "returns the assigned value when given a block" do
block = proc { nil }
subject.to_create(&block)
expect(subject.to_create).to eq block
FactoryBot::Definition.new(name)
expect(FactoryBot::DeclarationList).to have_received(:new).with(name)
end
end
describe "#name" do
it "returns the name" do
name = "factory name"
definition = described_class.new(name)
expect(definition.name).to eq(name)
end
end
describe "#overridable" do
let(:list) { double("declaration list", overridable: true) }
before do
allow(FactoryBot::DeclarationList).to receive(:new).and_return list
end
it "sets the declaration list as overridable" do
expect(subject.overridable).to eq subject
expect(list).to have_received(:overridable).once
end
end
describe "defining traits" do
let(:trait_1) { double("trait") }
let(:trait_2) { double("trait") }
it "maintains a list of traits" do
subject.define_trait(trait_1)
subject.define_trait(trait_2)
expect(subject.defined_traits).to include(trait_1, trait_2)
end
it "adds only unique traits" do
subject.define_trait(trait_1)
subject.define_trait(trait_1)
expect(subject.defined_traits.size).to eq 1
end
end
describe "adding callbacks" do
let(:callback_1) { "callback1" }
let(:callback_2) { "callback2" }
it "maintains a list of callbacks" do
subject.add_callback(callback_1)
subject.add_callback(callback_2)
expect(subject.callbacks).to eq [callback_1, callback_2]
end
end
describe "#to_create" do
its(:to_create) { should be_nil }
it "returns the assigned value when given a block" do
block = proc { nil }
subject.to_create(&block)
expect(subject.to_create).to eq block
end
end
end