RSpec: Flush table / destroy objects after each context - ruby-on-rails

How do you delete the objects (in the database and in the memory) you created
after each test
AND after each context? (in a context it could make sense to build tests on each other)
Is there a method to do this automatically?
I have the following problem:
Each test saves entries to the database. The next test then depends on these entries. Even if I wanted to build tests that are dependent on other tests, I couldn't, because the order in which the tests get executed is not controllable.
factories.rb:
sequence(:name) { |n| "purchaser #{n}" }
organization_spec.rb:
context "when no supplier exists" do
it "finds no associated suppliers" do
purchaser = create(:organization_purchaser)
purchaser.partners.empty?.should == true
end
end
context "when one supplier exists" do
it "finds one associated suppliers" do
purchaser = create(:organization_purchaser)
supplier = create(:organization_supplier)
partnership = create(:partnership, organization: purchaser, partner: supplier)
purchaser.partners.last.name.should == "purchaser 1"
end
end
context "when two suppliers exist" do
it "finds two associated suppliers" do
purchaser = create(:organization_purchaser)
2.times do |i|
supplier = create(:organization_supplier)
partnership = create(:partnership, organization: purchaser, partner: supplier)
end
purchaser.partners.last.name.should == "purchaser 2"
end
end
RSpec output:
Organization
#suppliers_for_purchaser
responds
when no supplier exists
finds no associated suppliers
when two suppliers exist
finds two associated suppliers
when one supplier exists
finds one associated suppliers (FAILED - 1)
Failures:
1) Organization#suppliers_for_purchaser when one supplier exists finds one associated suppliers
Failure/Error: purchaser.partners.last.name.should == "purchaser 1"
expected: "purchaser 1"
got: "purchaser 3" (using ==)

You should use Database Cleaner
All you have to do is add the following code to your Rspec configuration file spec_helper.rb
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
UPDATE
As of Rails 5.1 this is not needed if you use config.use_transactional_tests
https://github.com/rails/rails/pull/19282

Did you try adding a before method ?
describe MyController do
before(:each) do
User.delete_all
MyOtherModel.delete_all
...
end

I think your question is more along the lines of how can I reset a factory girl sequence? Those arent stored in the database, even if you delete all you will still have the issue. When testing something like this I find it easer to just override the factory girl sequence..
it "finds one associated suppliers" do
purchaser = create(:organization_purchaser)
supplier = create(:organization_supplier , name: "My First Supplier")
partnership = create(:partnership, organization: purchaser, partner: supplier)
purchaser.partners.last.name.should == "My First Supplier"
end
The other things you probably do is something like
it "finds one associated suppliers" do
purchaser = create(:organization_purchaser)
supplier = create(:organization_supplier)
partnership = create(:partnership, organization: purchaser, partner: supplier)
purchaser.partners.last.should == supplier
end
or even
it "finds one associated suppliers" do
purchaser = create(:organization_purchaser)
supplier = create(:organization_supplier)
partnership = create(:partnership, organization: purchaser, partner: supplier)
purchaser.partners.last.name.should == supplier.name
end

Related

How to write RSpec to check if associated record exists?

I am beginner to RSpec. I am having a model teacher that has_many :lessons. Here is my FactoryGirls records:
spec/factories/lessons.rb
FactoryGirl.define do
factory :lesson do
title "Rspec test"
description "test description"
company_name "narola pvt"
association :teacher
location "Zwanenplein 34"
days_to_pay 2
end
end
spec/factories/teachers.rb
FactoryGirl.define do
factory :teacher do
first_name "Teacher's name"
last_name "Teacher's last name"
address "los angeles"
city "california"
zip_code "12345"
country "USA"
birthdate nil
phone nil
password "password"
email { "example#{SecureRandom.uuid}#email.dummy" }
end
end
Following is my try with models test:
spec/models/teacher_spec.rb
require 'rails_helper'
RSpec.describe Teacher, type: :model do
let(:teacher) { FactoryGirl.create(:teacher) }
it "should have at least one lesson" do
config.expect_with(Lesson.where(teacher_id: teacher)){|c| c.syntax = :should}
end
end
I am willing to write a rspec test case to find if lesson exists for particular lesson.
Please try this:
it "should have at least one lesson" do
expect(Lesson.where(teacher_id: teacher.id)).to exist
end
Let me know if it's work for you. I haven't try this.
it "should have at least one lesson" do
expect(Lesson.where(teacher_id: teacher.id).exists?).to be_truthy
end
This would be faster because of the use of 'exists?' method compared to
expect(Lesson.where(teacher_id: teacher.id)).to exist
Underlying query execution due to use of 'exists?' the method is fast. More details are here ->
https://www.ombulabs.com/blog/benchmark/performance/rails/present-vs-any-vs-exists.html

Testing custom validation method with RSpec

I try to test validation method that check times overlap for activities.
There are three factories(two of them inherit from activity).
Factories:
activities.rb
FactoryGirl.define do
factory :activity do
name 'Fit Girls'
description { Faker::Lorem.sentence(3, true, 4) }
active true
day_of_week 'Thusday'
start_on '12:00'
end_on '13:00'
pool_zone 'B'
max_people { Faker::Number.number(2) }
association :person, factory: :trainer
factory :first do
name 'Swim Cycle'
description 'Activity with water bicycles.'
active true
day_of_week 'Thusday'
start_on '11:30'
end_on '12:30'
end
factory :second do
name 'Aqua Crossfit'
description 'Water crossfit for evereyone.'
active true
day_of_week 'Thusday'
start_on '12:40'
end_on '13:40'
pool_zone 'C'
max_people '30'
end
end
end
Activities overlaps when are on same day_of_week(activity.day_of_week == first.day_of_week), on same pool_zone(activity.pool_zone == first.pool_zone) and times overlaps.
Validation method:
def not_overlapping_activity
overlapping_activity = Activity.where(day_of_week: day_of_week)
.where(pool_zone: pool_zone)
activities = Activity.where(id: id)
if activities.blank?
overlapping_activity.each do |oa|
if (start_on...end_on).overlaps?(oa.start_on...oa.end_on)
errors.add(:base, "In this time and pool_zone is another activity.")
end
end
else
overlapping_activity.where('id != :id', id: id).each do |oa|
if (start_on...end_on).overlaps?(oa.start_on...oa.end_on)
errors.add(:base, "In this time and pool_zone is another activity.")
end
end
end
end
I wrote rspec test, but unfortunatelly invalid checks.
describe Activity, 'methods' do
subject { Activity }
describe '#not_overlapping_activity' do
let(:activity) { create(:activity) }
let(:first) { create(:first) }
it 'should have a valid factory' do
expect(create(:activity).errors).to be_empty
end
it 'should have a valid factory' do
expect(create(:first).errors).to be_empty
end
context 'when day_of_week, pool_zone are same and times overlap' do
it 'raises an error that times overlap' do
expect(activity.valid?).to be_truthy
expect(first.valid?).to be_falsey
expect(first.errors[:base].size).to eq 1
end
end
end
end
Return:
Failure/Error: expect(first.valid?).to be_falsey
expected: falsey value
got: true
I can't understand why it got true. First create(:activity) should be right, but next shouldn't be executed(overlapping).
I tried add expect(activity.valid?).to be truthy before expect(first.valid?..., but throws another error ActiveRecord::RecordInvalid. Could someone repair my test? I'm newbie with creation tests using RSpec.
UPDATE:
Solution for my problem is not create :first in test but build.
let(:first) { build(:first) }
This line on its own
let(:activity) { create(:activity) }
doesn't create an activity. It only creates an activity, when activity is actually called. Therefore you must call activity somewhere before running your test.
There are several ways to do so, for example a before block:
before { activity }
or you could use let! instead of just let.

Testing association validations with RSpec and FactoryGirl

I'm currently trying to write an RSpec test for a validation method. This method is triggered when the record is updated, saved or created. Here is what I have so far:
product.rb (model)
class Product < ActiveRecord::Base
validate :single_product
# Detects if a product has more than one SKU when attempting to set the single product field as true
# The sku association needs to map an attribute block in order to count the number of records successfully
# The standard self.skus.count is performed using the record ID, which none of the SKUs currently have
#
# #return [boolean]
def single_product
if self.single && self.skus.map { |s| s.active }.count > 1
errors.add(:single, " product cannot be set if the product has more than one SKU.")
return false
end
end
end
products.rb (FactoryGirl test data)
FactoryGirl.define do
factory :product do
sequence(:name) { |n| "#{Faker::Lorem.word}#{Faker::Lorem.characters(8)}#{n}" }
meta_description { Faker::Lorem.characters(10) }
short_description { Faker::Lorem.characters(15) }
description { Faker::Lorem.characters(20) }
sku { Faker::Lorem.characters(5) }
sequence(:part_number) { |n| "GA#{n}" }
featured false
active false
sequence(:weighting) { |n| n }
single false
association :category
factory :product_skus do
after(:build) do |product, evaluator|
build_list(:sku, 3, product: product)
end
end
end
end
product_spec.rb (unit test)
require 'spec_helper'
describe Product do
describe "Setting a product as a single product" do
let!(:product) { build(:product_skus, single: true) }
context "when the product has more than one SKU" do
it "should raise an error" do
expect(product).to have(1).errors_on(:single)
end
end
end
end
As you can see from the singe_product method, I'm trying to trigger an error on the single attribute when the single attribute is set to true and the product has more than one associated SKU. However, when running the test the product has no associated SKUs and therefore fails the unit test shown above.
How do I build a record and generate associated SKUs which can be counted (e.g: product.skus.count) and validated before they are all created in FactoryGirl?
You could write this like
it 'should raise an error' do
product = build(:product_skus, single: true)
expect(product).not_to be_valid
end

Testing an expected order of an array in RSpec / Rails

In a RSpec spec file I have the following test
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.find(:all,
:select => ["*,(abs(rating - current_user.rating)) as player_rating"],
:order => "player_rating",
:limit => 5)
# test that matched_players array returns what it is suppose to
end
How would I complete this to test that matched_players is returning the correct users.
I think you should first introduce some test users to the test DB (using for example a Factory) and afterwards see that the test is returning the correct ones.
Also it would make more sense to have a method in your model that would return the matched users.
For example:
describe "Player matching" do
before(:each) do
#user1 = FactoryGirl.create(:user, :rating => 5)
...
#user7 = FactoryGirl.create(:user, :rating => 3)
end
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.matched_players
matched_players.should eql [#user1,#user3,#user4,#user5,#user6]
end
end
Your model shouldn't know about your current user (the controllers know about this concept)
You need to extract this as a method on the User class otherwise there's no point in testing it, i.e. why test logic that isn't even in your app code?
The function that gets the matched players doesn't need to know about the current user, or any user for that matter, just the rating.
To test it, create a bunch of User instances, call the method, and see that the result is a list of the correct user instances you expect.
models/user.rb
class User < ActiveRecord::Base
...
def self.matched_players(current_user_rating)
find(:all,
select: ["*,(abs(rating - #{current_user_rating)) as match_strength"],
order: "match_strength",
limit: 5)
end
...
end
spec/models/user_spec.rb
describe User do
...
describe "::matched_players" do
context "when there are at least 5 users" do
before do
10.times.each do |n|
instance_variable_set "#user#{n}", User.create(rating: n)
end
end
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4.2)
matched_players.should == [#user4, #user5, #user3, #user6, #user2]
end
context "when multiple players have ratings close to the given rating and are equidistant" do
# we don't care how 'ties' are broken
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4)
matched_players[0].should == #user4
matched_players[1,2].should =~ [#user5, #user3]
matched_players[3,4].should =~ [#user6, #user2]
end
end
end
context "when there are fewer than 5 players in total" do
...
end
...
end
...
end

It is possible create records in controller specs?

I've wrote the follow example:
it "should assign #services containing all the current user services" do
customer = FactoryGirl.create(:user, fullname: "Iris Steensma")
sign_in customer
service = FactoryGirl.create(:service, user: customer)
puts "Service.count = #{Service.count}" # Service.count = 0
get :home
assigns[:services].should eq([service])
end
The action controller as:
def home
##services = curent_user.posted_services
#services = Service.all
end
And factories.rb contains:
FactoryGirl.define do
sequence :address do |n|
"Street #{n}"
end
factory :user do
fullname "Foo Bar"
email { "#{fullname.gsub(' ', '.').downcase}#example.com" if fullname }
password "secret"
end
factory :preference do
profile "customer"
user
end
factory :service do
status :pending
source_addr { generate(:address) }
target_addr { generate(:address) }
passenger "Mis Daysi"
start_at Time.now
offer 5
payment "cash"
user
end
end
Why Factory Girl can't create the Service record? The factory works fine in the test environment "rails c test"
Here is the rspec ouput:
Failures:
1) UsersController GET home should assign #services containing all the current user services
Failure/Error: assigns[:services].should eq([service])
expected: [#<Service:0x460d8ea #name="Service_1003">]
got: []
(compared using ==)
Diff:
## -1,2 +1,2 ##
-[#<Service:0x460d8ea #name="Service_1003">]
+[]
# ./spec/controllers/users_controller_spec.rb:26:in `block (3 levels) in <top (required)>'
Finished in 1.03 seconds
3 examples, 1 failure
I believe the correct syntax is:
assigns(:service).should eq([service])
According to the rspec documentation assigns[key] used to be the way to assign instance variables which looks a little like what's happening for you.
https://github.com/rspec/rspec-rails
First, the user: customer here is not needed, since you are using customer.services.create that already does that
service = customer.services.create(attributes_for(:service, user: customer))
Second, try this, after service = customer.services.create do something like
service.valid?
puts service.errors.inspect
puts service.user == customer
maybe you can also try
service = FactoryGirl.create(:service, :user => customer)
also, are you sure the association is defined on Service class?
doesn't
#<Service:0x57856f4 #name="Service_1003">
should be
#<Service:0x57856f4 #name="Service_1003" user="<your customer object">
?
how is your factory for service defined?

Resources