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')) @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Trait'))
@strategies = Registry.new('Strategy') @strategies = Registry.new('Strategy')
@callback_names = Set.new @callback_names = Set.new
@definition = Definition.new @definition = Definition.new(:configuration)
@allow_class_lookup = true @allow_class_lookup = true

View file

@ -1,9 +1,10 @@
module FactoryBot module FactoryBot
# @api private # @api private
class Definition 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) @declarations = DeclarationList.new(name)
@callbacks = [] @callbacks = []
@defined_traits = Set.new @defined_traits = Set.new

View file

@ -121,7 +121,9 @@ module FactoryBot
# #
# Except that no globally available sequence will be defined. # Except that no globally available sequence will be defined.
def sequence(name, *args, &block) 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) } add_attribute(name) { increment_sequence(sequence) }
end end

View file

@ -20,4 +20,80 @@ describe "FactoryBot.rewind_sequences" do
expect(email).to eq "somebody1@example.com" expect(email).to eq "somebody1@example.com"
expect(name).to eq "Joe" expect(name).to eq "Joe"
end 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 end

View file

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

View file

@ -1,19 +1,29 @@
describe FactoryBot::Definition do describe FactoryBot::Definition do
subject { described_class.new(:name) }
it { should delegate(:declare_attribute).to(:declarations) } 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 it "creates a new attribute list with the name passed" do
name = "great name"
allow(FactoryBot::DeclarationList).to receive(:new) allow(FactoryBot::DeclarationList).to receive(:new)
subject
FactoryBot::Definition.new(name)
expect(FactoryBot::DeclarationList).to have_received(:new).with(name) expect(FactoryBot::DeclarationList).to have_received(:new).with(name)
end end
end end
describe FactoryBot::Definition, "#overridable" do 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) } let(:list) { double("declaration list", overridable: true) }
before do before do
allow(FactoryBot::DeclarationList).to receive(:new).and_return list allow(FactoryBot::DeclarationList).to receive(:new).and_return list
@ -23,9 +33,9 @@ describe FactoryBot::Definition, "#overridable" do
expect(subject.overridable).to eq subject expect(subject.overridable).to eq subject
expect(list).to have_received(:overridable).once expect(list).to have_received(:overridable).once
end end
end end
describe FactoryBot::Definition, "defining traits" do describe "defining traits" do
let(:trait_1) { double("trait") } let(:trait_1) { double("trait") }
let(:trait_2) { double("trait") } let(:trait_2) { double("trait") }
@ -40,9 +50,9 @@ describe FactoryBot::Definition, "defining traits" do
subject.define_trait(trait_1) subject.define_trait(trait_1)
expect(subject.defined_traits.size).to eq 1 expect(subject.defined_traits.size).to eq 1
end end
end end
describe FactoryBot::Definition, "adding callbacks" do describe "adding callbacks" do
let(:callback_1) { "callback1" } let(:callback_1) { "callback1" }
let(:callback_2) { "callback2" } let(:callback_2) { "callback2" }
@ -51,9 +61,9 @@ describe FactoryBot::Definition, "adding callbacks" do
subject.add_callback(callback_2) subject.add_callback(callback_2)
expect(subject.callbacks).to eq [callback_1, callback_2] expect(subject.callbacks).to eq [callback_1, callback_2]
end end
end end
describe FactoryBot::Definition, "#to_create" do describe "#to_create" do
its(:to_create) { should be_nil } its(:to_create) { should be_nil }
it "returns the assigned value when given a block" do it "returns the assigned value when given a block" do
@ -61,4 +71,5 @@ describe FactoryBot::Definition, "#to_create" do
subject.to_create(&block) subject.to_create(&block)
expect(subject.to_create).to eq block expect(subject.to_create).to eq block
end end
end
end end