Rails Rspec test with carrierwave image - ruby-on-rails

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

Related

Duplicate associations in Factory Girl

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.

Active Record find from factory girl

Im trying to implement a database-based sequence generator in rails so i wrote a code that goes something like
#semaphore.synchronize {
seq = Sequence.find_by_name(type)
seq.value += 1
seq.save
val = seq.value
unless prefix.nil?
"#{prefix}-#{val}"
else
"#{val}"
end
}
My question is, is it possible to setup the initial sequence data using factory girl and be able to access it using Sequence.find_by_name or fixture loading is my only option? i.e. rake db:fixtures:load RAILS_ENV=test FIXTURES_PATH=spec/fixtures?
Thanks
Ok, i was able to find the answer.
I created my Sequence definition in factory girl as below:
FactoryGirl.define do
factory :membership_sequence, class: Sequence do
name 'membership'
value 1
end
factory :payment_sequence, class: Sequence do
name 'payment'
value 1
end
end
Then, in the before(:all) I called the create() method
before(:all) do
# create the sequences
create(:membership_sequence)
create(:payment_sequence)
end
and voila! the Sequence.find_by_name shall work now.
To answer your question why i choose to implement sequence generation manually than using rails active record autogenerated ids, it's because i want to be able to generate sequences such as PREFIX-2015-0001, PREFIX-2016-0001

Rspec/FactoryGirl uniqueness validations in a large test suite

Using Rspec and FactoryGirl, if I have a factory that autoincrements a trait using a sequence, and in some specs if I explicitly set this trait, with a large enough test suite, sometimes random specs fail with
Validation failed: uniq_id has already been taken
The factory is defined like this:
factory :user { sequence(:uniq_id) {|n| n + 1000} }
I'm guessing this validation fails because in one place in my test suite, I generate a user like this:
create(:user, uniq_id: 5555)
And because presumably factory girl is generating more than 4,555 users over the suite, the validation is failing?
I'm attempting to avoid this problem by just turning the uniq_id into 55555 (larger number), so there is no interference. But is there a better solution? My spec_helper includes these relevant bits:
config.use_transactional_fixtures = true
config.after(:all) do
DatabaseCleaner.clean_with(:truncation)
end
It happens to me sometimes. I didn't found any explanation, but happens only with big set of data. I let someone find the explanation!
When it happens, you can declare your attribute like this (here is an example using faker gem) :
FactoryGirl.define do
factory :user do
login do
# first attempt
l = Faker::Internet.user_name
while User.exists?(:login => l) do
# Here is a loop forcing validation
l = Faker::Internet.user_name
end
l # return login
end
end
end
I was able to solve my issue like this in my factory (based on #gotva's suggestion in the question comments).
factory :user do
sequence(:uniq_id) { |n| n + 1000 }
# increment again if somehow invalid
after(:build) do |obj|
if !obj.valid? && obj.errors.keys.include?(:uniq_id)
obj.uniq_id +=1
end
end
end

avoid factorygirl repeate the number when run second time in cucumber

Its my factorygirl code
FactoryGirl.define do
factory :account do
sequence :name do |n|
"Test Account#{n}"
end
end
end
This is my method for run factorygirl code
def create_accounts n=2
n.times do
FactoryGirl.create(:account, subscription_ids: #sub.id.to_s)
end
end
My problem is, first time my FactoryGirl output is Test Account1, Test Account2, When i execute second time, It create output as Test Account3, Test Account4. But I need Test Account1, Test Account2 when run multiple time. How may i do this.
Thanks for your advices
FactoryGirl is designed to create new unique records every time you call #create. If you want to keep the original record set around, you should save them to a variable and then return them rather than running FactoryGirl.create again.
You can also use database_cleaner gem to clean the database after every test. This helps to prevent any problems rising from the state of the database.
I solve this problem after replace this code
def create_accounts n=1
create_subscription
n.times do |r|
r += 1
FactoryGirl.create(:account, subscription_ids: #sub.id.to_s, name: "Test Account#{r}")
end
end
Updated
I am using cucumber-> capybara -> selenium
Reset the factory girl sequence
add this code in spec->support->reset.rb
module FactoryGirl
def self.reload
self.factories.clear
self.sequences.clear
self.traits.clear
self.find_definitions
end
end
Add this in env.rb
After do
FactoryGirl.reload
end

File upload, factory_girl & database_cleaner

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

Resources