Provided that I have a project factory
Factory.define :project do |p|
p.sequence(:title) { |n| "project #{n} title" }
p.sequence(:subtitle) { |n| "project #{n} subtitle" }
p.sequence(:image) { |n| "../images/content/projects/#{n}.jpg" }
p.sequence(:date) { |n| n.weeks.ago.to_date }
end
And that I'm creating instances of project
Factory.build :project
Factory.build :project
By this time, the next time I execute Factory.build(:project) I'll receive an instance of Project with a title set to "project 3 title" and so on. Not sur开发者_StackOverflow社区prising.
Now say that I wish to reset my counter within this scope. Something like:
Factory.build :project #=> Project 3
Factory.reset :project #=> project factory counter gets reseted
Factory.build :project #=> A new instance of project 1
What would be the best way to achieve this?
I'm currently using the following versions:
factory_girl (1.3.1) factory_girl_rails (1.0)
Just call FactoryGirl.reload in your before/after callback. This is defined in the FactoryGirl codebase as:
module FactoryGirl
def self.reload
self.factories.clear
self.sequences.clear
self.traits.clear
self.find_definitions
end
end
Calling FactoryGirl.sequences.clear is not sufficient for some reason. Doing a full reload might have some overhead, but when I tried with/without the callback, my tests took around 30 seconds to run either way. Therefore the overhead is not enough to impact my workflow.
After tracing my way through the source code, I have finally come up with a solution for this. If you're using factory_girl 1.3.2 (which was the latest release at the time I am writing this), you can add the following code to the top of your factories.rb file:
class Factory
def self.reset_sequences
Factory.factories.each do |name, factory|
factory.sequences.each do |name, sequence|
sequence.reset
end
end
end
def sequences
@sequences
end
def sequence(name, &block)
s = Sequence.new(&block)
@sequences ||= {}
@sequences[name] = s
add_attribute(name) { s.next }
end
def reset_sequence(name)
@sequences[name].reset
end
class Sequence
def reset
@value = 0
end
end
end
Then, in Cucumber's env.rb, simply add:
After do
Factory.reset_sequences
end
I'd assume if you run into the same problem in your rspec tests, you could use rspecs after :each method.
At the moment, this approach only takes into consideration sequences defined within a factory, such as:
Factory.define :specialty do |f|
f.sequence(:title) { |n| "Test Specialty #{n}"}
f.sequence(:permalink) { |n| "permalink#{n}" }
end
I have not yet written the code to handle: Factory.sequence...
For googling people: without further extending, just do FactoryGirl.reload
FactoryGirl.create :user
#=> User id: 1, name: "user_1"
FactoryGirl.create :user
#=> User id: 2, name: "user_2"
DatabaseCleaner.clean_with :truncation #wiping out database with truncation
FactoryGirl.reload
FactoryGirl.create :user
#=> User id: 1, name: "user_1"
works for me on
* factory_girl (4.3.0)
* factory_girl_rails (4.3.0)
https://stackoverflow.com/a/16048658
There is a class method called sequence_by_name
to fetch a sequence by name, and then you can call rewind
and it'll reset to 1.
FactoryBot.sequence_by_name(:order).rewind
Or if you want to reset all.
FactoryBot.rewind_sequences
Here is the link to the file on github
According to ThoughBot Here, the need to reset the sequence between tests is an anti-pattern.
To summerize:
If you have something like this:
FactoryGirl.define do
factory :category do
sequence(:name) {|n| "Category #{n}" }
end
end
Your tests should look like this:
Scenario: Create a post under a category
Given a category exists with a name of "My Category"
And I am signed in as an admin
When I go to create a new post
And I select "My Category" from "Categories"
And I press "Create"
And I go to view all posts
Then I should see a post with the category "My Category"
Not This:
Scenario: Create a post under a category
Given a category exists
And I am signed in as an admin
When I go to create a new post
And I select "Category 1" from "Categories"
And I press "Create"
And I go to view all posts
Then I should see a post with the category "Category 1"
Had to ensure sequences are going from 1 to 8 and restart to 1 and so on. Implemented like this:
class FGCustomSequence
def initialize(max)
@marker, @max = 1, max
end
def next
@marker = (@marker >= @max ? 1 : (@marker + 1))
end
def peek
@marker.to_s
end
end
FactoryGirl.define do
factory :image do
sequence(:picture, FGCustomSequence.new(8)) { |n| "image#{n.to_s}.png" }
end
end
The doc says "The value just needs to support the #next
method." But to keep you CustomSequence object going through it needs to support #peek
method too. Lastly I don't know how long this will work because it kind of hack into FactoryGirl internals, when they make a change this may fail to work properly
There's no built in way to reset a sequence, see the source code here:
http://github.com/thoughtbot/factory_girl/blob/master/lib/factory_girl/sequence.rb
However, some people have hacked/monkey-patched this feature in. Here's an example:
http://www.pmamediagroup.com/2009/05/smarter-sequencing-in-factory-girl/
To reset particular sequence you can try
# spec/factories/schedule_positions.rb
FactoryGirl.define do
sequence :position do |n|
n
end
factory :schedule_position do
position
position_date Date.today
...
end
end
# spec/models/schedule_position.rb
require 'spec_helper'
describe SchedulePosition do
describe "Reposition" do
before(:each) do
nullify_position
FactoryGirl.create_list(:schedule_position, 10)
end
end
protected
def nullify_position
position = FactoryGirl.sequences.find(:position)
position.instance_variable_set :@value, FactoryGirl::Sequence::EnumeratorAdapter.new(1)
end
end
If you are using Cucumber you can add this to a step definition:
Given(/^I reload FactoryGirl/) do
FactoryGirl.reload
end
Then just call it when needed.
精彩评论