In my model, I have to choose an asset, saved in a editorial_asset table.
include ActionDispatch::TestProcess
FactoryGirl.define do
factory :editorial_asset do
editorial_asset { fixture_file_upload("#{Rails.root}/spec/fixtures/files/fakeUp.png", "image/png") }
end
end
so I have attached in my model factory an association on :editorial_asset
Upload work great, but take too much time (1s per example)
I'm wonder if it's possible to create uploads one time before each examples, and say in the factory: "find instead of create"
But the problem with database_cleaner, I cannot except tables with :transaction, truncation take 25sec instead of 40ms !
EDIT
The factory that need an asset
FactoryGirl.define do
factory :actu do
sequence(:title) {|n| "Actu #{n}"}
sequence(:subtitle) {|n| "Sous-sitre #{n}"}
body Lipsum.paragraphs[3]
# Associations
user
# editorial_asset
end
end
The model spec
require 'spec_helper'
describe Actu do
before(:all) do
#asset = create(:editorial_asset)
end
after(:all) do
EditorialAsset.destroy_all
end
it "has a valid factory" do
create(:actu).should be_valid
end
end
So a working way is
it "has a valid factory" do
create(:actu, editorial_asset: #asset).should be_valid
end
but there's no way to inject automatically association ?
Since you're using RSpec, you could use a before(:all) block to set up these records once. However, anything done in a before-all block is NOT considered part of the transaction, so you will have to delete anything from the DB yourself in an after-all block.
Your factory for the model that has an association to the editorial asset could then, yes, try to first find one before creating it. Instead of doing something like association :editorial_asset you could do:
editorial_asset { EditorialAsset.first || Factory.create(:editorial_asset) }
Your rspec tests could then look like this:
before(:all) do
#editorial = Factory.create :editorial_asset
end
after(:all) do
EditorialAsset.destroy_all
end
it "already has an editorial asset." do
model = Factory.create :model_with_editorial_asset
model.editorial_asset.should == #editorial
end
Read more about before and after blocks on the Rspec GitHub wiki page or on the Relish documentation:
https://github.com/rspec/rspec-rails
https://www.relishapp.com/rspec
Related
I wrote an API which can return latest 5 Newsletter and its image, but I am stuck at writing its rspec test.
First of all, here is the relationship between model.
Newsletter has_many NewsletterImages
NewsletterImage belong_to Newsletter
Secondly, I thought that I need to create some data in test database, so I wrote following code in rspec file.
7.times do |i|
n = Newsletter.create(title: "Test#{i}", content: "TestContents#{i}")
2.times do |i|
ni = NewsletterImage.create(newsletter_id: n.id, order: i)
ni.image = File.open('xxx.png')
ni.save
end
end
So, I need to upload file in very test? Is there a better way to generate data and test?
Better to use Factory Girl to make your test data. That way, you can write clean tests like
# /spec/factories/newsletter_factory.rb
FactoryGirl.define do
factory :newsletter do
title "My newsletter"
content "Some content"
end
end
# /spec/factories/newsletter_image_factory.rb
FactoryGirl.define do
factory :newsletter_image do
newsletter
image fixture_file_upload( Rails.root + 'spec/fixtures/images/example.jpg', "image/jpg")
end
end
# spec/models/newsletter_spec.rb
image = create :newsletter_image
expect(image.newsletter.title).to eq 'My Newsletter'
With all of the details of how the models are created hidden in the factory definition files, it's then easy to share the code across many tests.
For more detail about adding carrierwave files to Factory Girl definitions, look for other answers such as this one: https://stackoverflow.com/a/9952914/693349
I am using fixtures to load test data on a Ruby on Rails project. I moved to Factory Girl, but I am getting duplicate entries for associations.
I have a Group model and a Value model. A Group can have multiple Values.
Also, I am using Cucumber for my tests. And FactoryGirl.lint to populate the database.
My fixtures
groups.yml
group_1:
name: "Flavour"
values.yml
value_1:
name: "Strawberry"
group: group_1
value_2:
name: "Mint"
group: group_1
value_3:
name: "Chocolate"
group: group_1
This works just fine. A single Group is created and the 3 Values are attached to it.
My factories
groups.rb
FactoryGirl.define do
factory :group do
name "Flavour"
end
end
values.rb
FactoryGirl.define do
factory :value_1, class: :value do
name "Strawberry"
group
end
factory :value_2, class: :value do
name "Mint"
group
end
factory :value_3, class: :value do
name "Chocolate"
group
end
end
This is not working. Factory Girl is creating 3 Groups, each one associated to 1 Value.
Is this normal behaviour for Factory Girl?
Also, I read that use_transactional_fixtures should be set to true. This is already the case.
I don't think you really have grasped the conceptual differences between using fixtures and factories.
With fixtures you have these static object definitions that get chucked into the database on each test run. Its like your database has a "zero" state with a bunch of data already. Feels nice, warm and fuzzy (Oh I don't have to set everything up, so nice!) but its a horrible idea in reality since it masks any errors caused by an empty table!
With factories you define objects dynamically instead and create them when needed. You use a tool like database_cleaner to clean out any residual state between tests. You start each test with nothing.
FactoryGirl.lint does not populate the database. Its a linter that checks that your factory definitions are correct.
There is absolutely zero point in using factory_girl if you just create a bunch of fixtures with it. So instead you want to do something like:
FactoryGirl.define do
factory :value do
name { ["Strawberry", "Mint", "Chocolate"].sample }
group
end
end
One of the mental hurdles of going from fixtures to factories is you have to stop writing tests like if you where using fixture data:
describe "GET /users/:id" do
let!(:user){ FactoryGirl.create(:user) }
let(:json) { JSON.parse(response.body) }
before { get users_path(#user), format: :json }
it "has the correct name" do
# bad
expect(json["name"]).to eq "John Doe" # strong coupling to factory!
# good
expect(json["name"]).to eq user.name # we don't know what the factory generates.
end
end
If you have to have a test where a certain factory value must be known or if a factory needs to be associated to a certain object then do it explicitly instead of making a mess of your definitions.
let(:user) { FactoryGirl.create(:user, name: 'Max') }
let(:item) { user.items.create(FactoryGirl.attributes_for(:item)) }
it "has an awesome name" do
expect(user.name).to eq 'Max'
end
it "is associated" do
expect(item.user).to eq user
end
Your value factories are told to use the group factory for their group, and the group factory is told to make a new group with name 'Flavor'. What you want to do differently is that you want the group factory to return the existing 'Flavor' rather than a new one. Here is how to do that.
FactoryGirl.define do
factory :group, class: :group do
name 'Flavor'
initialize_with {Group.find_or_initialize_by(name: name)}
end
end
That is saying if you already have a group with desired name, use existing group. Written as above so that there is a default name, and you get the same 'use existing if present' behavior when passing in an alternate name.
When I run rake db:test:prepare,
It automagically generates my Factories :
require 'ffaker'
FactoryGirl.define do
factory :user do
sequence(:email) {|i| "marley_child#{i}#gmail.com" }
password 'secret_shhh'
end
factory :brain do
user FactoryGirl.create :user
end
end
And then if I try to run rspec or even access my console with rails c test, I get a validation error :
/activerecord-3.2.6/lib/active_record/validations.rb:56:in `save!': Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
My Rspec :
describe '#email' do
context 'uniqueness' do
let(:user) { FactoryGirl.build :user, email: 'Foo#Bar.COM' }
subject { user.errors }
before do
FactoryGirl.create :user, email: 'foo#bar.com'
user.valid?
end
its(:messages) { should include(email: ['has already been taken']) }
end
end
What makes no sense to me is I assumed this data was transactional. Why are my factories getting generated when I prepare by data and not within each test? What is the most appropriate way to do this?
Well, one problem is that in your :brain factory definition, you're actually calling FactoryGirl.create :user as part of the definition of the factory when you presumably meant to call it when the factory is invoked (i.e. user {FactoryGirl.create :user}).
As for why there is already a User in the database, I can't answer that except to say that sometimes even if you're running with transactions turned on and things go south, records can be left behind.
I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end
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 surprising.
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...
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
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
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.