I have a named scope in rails and i have model by name Product
class Product < ApplicationRecord
scope :old_products, -> { where("tagged_with = ?","old") }
end
Have any body encountered the process of checking the subject which is using where in the active record and that can check the what where clause does the named scope actually holds
In rspec spec/models/product_spec.rb
describe Product do
describe "checking scope clauses" do
subject { Product.old_products }
its(:where_clauses) { should eq([
"tagged_with = 'old'"
]) }
end
end
end
By the way i use rspec-2.89 version with the rails-5 version so any chances that we can check and verify the where clauses
I personally don't think checking returned SQL of a scope is sufficient. The way I would test old_products is:
describe Product do
describe "scopes" do
describe "old_products" do
let!(:old_product) {Product.create(tagged_with: 'old')}
let!(:not_old_product) {Product.create(tagged_with: 'sth_else')}
subject { Product.old_products }
it "returns the product(s) with tagged_with = old" do
expect(subject).to eq([old_product])
end
end
end
end
If you still want to check the return query, may want to try:
it "..." do
expect(subject.to_sql).to eq("SELECT \"products\".* FROM \"products\" WHERE \"products\".\"tagged_with\" = 'old'")
end
# This is better, more readable syntax for scope declaration
class Product < ApplicationRecord
scope :old_products, -> { where(tagged_with: 'old') }
end
# Something like this would work
describe Product do
context 'scopes' do
# Set up something that will always be excluded from the scopes
let!(:product) { create :product }
let!(:scoped_list) { create :product, 3, tagged_with: tag }
shared_examples_for 'returns scoped records' do
# This should work with shoulda-matchers
# (https://github.com/thoughtbot/shoulda-matchers)
# Could also not use subject and do something like:
# expect(
# described_class.send(scope_name.to_sym)
# ).to contain_exactly(scoped_list)
# and declare let(:scope_name) in your describe blocks
it 'returns scoped products' do
should contain_exactly(scoped_list)
end
end
describe '.old_products' do
subject(:old_products) { described_class.old_products }
let(:tag) { 'old' }
it_behaves_like 'returns scoped records'
end
describe '.other_scope' do
subject(:other_scope) { described_class.other_scope }
let(:tag) { 'other_tag' }
it_behaves_like 'returns scoped records'
end
end
end
There is no value in testing the actual SQL -- it's generated by Rails; what you want to test is that your scope is returning the correct objects
I would use context instead of describe when declaring the scopes test block because you're not describing a class or instance method
Use single quotes instead of double quotes unless you are doing string interpolation -- it's more performant
If you're early in the project and each Product only has a single tag, I would also rename the tagged_with column to be tag
describe Product do
describe "checking scope clauses" do
subject { Product.old_products }
expect(subject.values[:where].instance_values['predicates'].to eq(["tagged_with = 'old'"])
end
end
I have an object that takes the information from a rest API. Let's say I call Posts from an API and I can specify which fields I want to extract. Just an example of how it looks:
Post.find!(id: 1, fields: [:id, :title, :description])
This does an API call and it returns me a Post object with these specified fields.
For testing, I am stubbing this API call with Factory Bot and returns directly a Post object with all possible fields it can query.
This approach is not the best since the tests are always returning all fields and code itself maybe I just need a couple of fields not all of them
So I am trying to achieve something like (in FactoryBot):
build(:post, fields: [:id,:title]) and set up a Post object just with id and title.
If I do build(:post, fields: [:title, :created_at]) and set up a Post object just with title and created_at. And so on...
Did some research and trying some ideas, but failed in all of them about how to build this flow.
Any idea about how to achieve this behavior?
EDIT
Traits seems a nice alternative, but I must be as consistent as the API call, specifying these fields. So traits are not working for me...
Let's assume that this is your factory for the Post:
FactoryBot.define do
factory :post do
sequence(:title) { |n| "Post no. #{n}" }
description 'Post description'
created_at { DateTime.now }
end
end
When you call build(:post) it will create an object with title, created_at and description set.
But if you will remove those fields from your factory (or move them under trait):
FactoryBot.define do
factory :post do
trait :all_fields do
sequence(:title) { |n| "Post no. #{n}" }
description 'Post description'
created_at { DateTime.now }
end
end
end
Then:
calling build(:post, title: 'Post title', description: 'Post description') will create a post object where only title and description are set (created_at will be nil)
calling build(:post, :all_fields) will create a post object will all fields set.
Edit
I think I understand the problem better now. Let's assume that this is you factory:
FactoryBot.define do
factory :post do
sequence(:title) { |n| "Post no. #{n}" }
created_at { DateTime.now }
description 'Post description'
end
end
Change it to:
FactoryBot.define do
factory :post do
transient do
fields []
end
sequence(:title) { |n| "Post no. #{n}" }
created_at { DateTime.now }
description 'Post description'
after :build do |post, evaluator|
unless evaluator.fields.empty? do
(post.attributes.keys - evaluator.fields).each do |attribute_to_remove|
post.send("#{attribute_to_remove}=", nil)
end
end
end
end
end
Then, you can call it like this:
build(:post) creates post with all fields
build(:post, fields: ['description', 'title']) creates a post where everything except description and title is nil.
This solution should work as expected but it might slow down your tests (and I think it does not look nice :) )
FactoryBot lets you override the factory by passing a hash of attributes - so why not just set the attributes to nil:
build(:post, {
title: nil,
description: nil
})
Best practice in FactoryBot is to have your standard factory produce objects that contain only the required fields for a model. (Note that FactoryBot will generate an id for you automatically.)
Assume you have a model called Post that requires only a title, but has optional fields description and date. Your factory would look like this:
FactoryBot.define do
factory :post do
title 'Post Title'
end
end
Now when you build a Post, it will look like this:
>> FactoryBot.build_stubbed(:post)
#> <Post id: 1001, title: "Post Title", description: nil, date: nil>
You can add the optional fields on a case-by-case basis:
>> FactoryBot.build_stubbed(:post, description: 'This is a test post.')
#> <Post id: 1001, title: "Post Title", description: 'This is a test post.', date: nil>
Or you can add traits within the factory:
FactoryBot.define do
factory :post do
title 'Post Title'
trait :with_description_and_date do
description 'This is a test post.'
date Date.today
end
end
end
>> FactoryBot.build_stubbed(:post, :with_description_and_date)
#> <Post id: 1001, title: "Post Title", description: 'This is a test post.', date: Thu, 12 Apr 2018>
I'm using scaffolding to generate rspec controller tests. By default, it creates the test as:
let(:valid_attributes) {
skip("Add a hash of attributes valid for your model")
}
describe "PUT update" do
describe "with valid params" do
let(:new_attributes) {
skip("Add a hash of attributes valid for your model")
}
it "updates the requested doctor" do
company = Company.create! valid_attributes
put :update, {:id => company.to_param, :company => new_attributes}, valid_session
company.reload
skip("Add assertions for updated state")
end
Using FactoryGirl, I've filled this in with:
let(:valid_attributes) { FactoryGirl.build(:company).attributes.symbolize_keys }
describe "PUT update" do
describe "with valid params" do
let(:new_attributes) { FactoryGirl.build(:company, name: 'New Name').attributes.symbolize_keys }
it "updates the requested company", focus: true do
company = Company.create! valid_attributes
put :update, {:id => company.to_param, :company => new_attributes}, valid_session
company.reload
expect(assigns(:company).attributes.symbolize_keys[:name]).to eq(new_attributes[:name])
This works, but it seems like I should be able to test all attributes, instead of just testing the changed name. I tried changing the last line to:
class Hash
def delete_mutable_attributes
self.delete_if { |k, v| %w[id created_at updated_at].member?(k) }
end
end
expect(assigns(:company).attributes.delete_mutable_attributes.symbolize_keys).to eq(new_attributes)
That almost worked, but I'm getting the following error from rspec having to do with BigDecimal fields:
-:latitude => #<BigDecimal:7fe376b430c8,'0.8137713195 830835E2',27(27)>,
-:longitude => #<BigDecimal:7fe376b43078,'-0.1270954650 1027958E3',27(27)>,
+:latitude => #<BigDecimal:7fe3767eadb8,'0.8137713195 830835E2',27(27)>,
+:longitude => #<BigDecimal:7fe3767ead40,'-0.1270954650 1027958E3',27(27)>,
Using rspec, factory_girl, and scaffolding is incredibly common, so my questions are:
What is a good example of an rspec and factory_girl test for a PUT update with valid params?
Is it necessary to use attributes.symbolize_keys and to delete the mutable keys? How can I get those BigDecimal objects to evaluate as eq?
Ok so this is how I do, I don't pretend to strictly follow the best practices, but I focus on precision of my tests, clarity of my code, and fast execution of my suite.
So let take example of a UserController
1- I do not use FactoryGirl to define the attributes to post to my controller, because I want to keep control of those attributes. FactoryGirl is useful to create record, but you always should set manually the data involved in the operation you are testing, it's better for readability and consistency.
In this regard we will manually define the posted attributes
let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }
2- Then I define the attributes I expect for the updated record, it can be an exact copy of the posted attributes, but it can be that the controller do some extra work and we also want to test that. So let's say for our example that once our user updated his personal information our controller automatically add a need_admin_validation flag
let(:expected_update_attributes) { valid_update_attributes.merge(need_admin_validation: true) }
That's also where you can add assertion for attribute that must remain unchanged. Example with the field age, but it can be anything
let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }
3- I define the action, in a let block. Together with the previous 2 let I find it makes my specs very readable. And it also make easy to write shared_examples
let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }
4- (from that point everything is in shared example and custom rspec matchers in my projects) Time to create the original record, for that we can use FactoryGirl
let!(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }
As you can see we manually set the value for age as we want to verify it did not change during the update action. Also, even if the factory already set the age to 25 I always overwrite it so my test won't break if I change the factory.
Second thing to note: here we use let! with a bang. That is because sometimes you may want to test your controller's fail action, and the best way to do that is to stub valid? and return false. Once you stub valid? you can't create records for the same class anymore, therefor let! with a bang would create the record before the stub of valid?
5- The assertions itself (and finally the answer to your question)
before { action }
it {
assert_record_values record.reload, expected_update_attributes
is_expected.to redirect_to(record)
expect(controller.notice).to eq('User was successfully updated.')
}
Summarize So adding all the above, this is how the spec looks like
describe 'PATCH update' do
let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }
let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }
let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }
let(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }
before { action }
it {
assert_record_values record.reload, expected_update_attributes
is_expected.to redirect_to(record)
expect(controller.notice).to eq('User was successfully updated.')
}
end
assert_record_values is the helper that will make your rspec simpler.
def assert_record_values(record, values)
values.each do |field, value|
record_value = record.send field
record_value = record_value.to_s if (record_value.is_a? BigDecimal and value.is_a? String) or (record_value.is_a? Date and value.is_a? String)
expect(record_value).to eq(value)
end
end
As you can see with this simple helper when we expect for a BigDecimal, we can just write the following, and the helper do the rest
let(:expected_update_attributes) { {latitude: '0.8137713195'} }
So at the end, and to conclude, when you have written your shared_examples, helpers, and custom matchers, you can keep your specs super DRY. As soon as you start repeating the same thing in your controllers specs find how you can refactor this. It may take time at first, but when its done you can write the tests for a whole controller in few minutes
And a last word (I can't stop, I love Rspec) here is how my full helper look like. It is usable for anything in fact, not just models.
def assert_records_values(records, values)
expect(records.length).to eq(values.count), "Expected <#{values.count}> number of records, got <#{records.count}>\n\nRecords:\n#{records.to_a}"
records.each_with_index do |record, index|
assert_record_values record, values[index], index: index
end
end
def assert_record_values(record, values, index: nil)
values.each do |field, value|
record_value = [field].flatten.inject(record) { |object, method| object.try :send, method }
record_value = record_value.to_s if (record_value.is_a? BigDecimal and value.is_a? String) or (record_value.is_a? Date and value.is_a? String)
expect_string_or_regexp record_value, value,
"#{"(index #{index}) " if index}<#{field}> value expected to be <#{value.inspect}>. Got <#{record_value.inspect}>"
end
end
def expect_string_or_regexp(value, expected, message = nil)
if expected.is_a? String
expect(value).to eq(expected), message
else
expect(value).to match(expected), message
end
end
This is the questioner posting. I had to go down the rabbit hole a bit in understanding multiple, overlapping issues here, so I just wanted to report back on the solution I found.
tldr; It's too much trouble trying to confirm that every important attribute comes back unchanged from a PUT. Just check that the changed attribute is what you expect.
The issues I encountered:
FactoryGirl.attributes_for does not return all values, so FactoryGirl: attributes_for not giving me associated attributes suggests using (Factory.build :company).attributes.symbolize_keys, which winds up creating new problems.
Specifically, Rails 4.1 enums show as integers instead of enum values, as reported here: https://github.com/thoughtbot/factory_girl/issues/680
It turns out that the BigDecimal issue was a red herring, caused by a bug in the rspec matcher which produces incorrect diffs. This was established here: https://github.com/rspec/rspec-core/issues/1649
The actual matcher failure is caused by Date values that don't match. This is due to the time returned being different, but it doesn't show because Date.inspect does not show milliseconds.
I got around these problems with a monkey patched Hash method that symbolizes keys and stringifes values.
Here's the Hash method, which could go in rails_spec.rb:
class Hash
def symbolize_and_stringify
Hash[
self
.delete_if { |k, v| %w[id created_at updated_at].member?(k) }
.map { |k, v| [k.to_sym, v.to_s] }
]
end
end
Alternatively (and perhaps preferably) I could have written a custom rspec matcher than iterates through each attribute and compares their values individually, which would have worked around the date issue. That was the approach of the assert_records_values method at the bottom of the answer I selected by #Benjamin_Sinclaire (for which, thank you).
However, I decided instead to go back to the much, much simpler approach of sticking with attributes_for and just comparing the attribute I changed. Specifically:
let(:valid_attributes) { FactoryGirl.attributes_for(:company) }
let(:valid_session) { {} }
describe "PUT update" do
describe "with valid params" do
let(:new_attributes) { FactoryGirl.attributes_for(:company, name: 'New Name') }
it "updates the requested company" do
company = Company.create! valid_attributes
put :update, {:id => company.to_param, :company => new_attributes}, valid_session
company.reload
expect(assigns(:company).attributes['name']).to match(new_attributes[:name])
end
I hope this post allows others to avoid repeating my investigations.
Well, I did something that's quite simpler, I'm using Fabricator, but I'm pretty sure it's the same with FactoryGirl:
let(:new_attributes) ( { "phone" => 87276251 } )
it "updates the requested patient" do
patient = Fabricate :patient
put :update, id: patient.to_param, patient: new_attributes
patient.reload
# skip("Add assertions for updated state")
expect(patient.attributes).to include( { "phone" => 87276251 } )
end
Also, I'm not sure why you are building a new factory, PUT verb is supposed to add new stuff, right?. And what you are testing if what you added in the first place (new_attributes), happens to exist after the put in the same model.
This code can be used to solve your two issues:
it "updates the requested patient" do
patient = Patient.create! valid_attributes
patient_before = JSON.parse(patient.to_json).symbolize_keys
put :update, { :id => patient.to_param, :patient => new_attributes }, valid_session
patient.reload
patient_after = JSON.parse(patient.to_json).symbolize_keys
patient_after.delete(:updated_at)
patient_after.keys.each do |attribute_name|
if new_attributes.keys.include? attribute_name
# expect updated attributes to have changed:
expect(patient_after[attribute_name]).to eq new_attributes[attribute_name].to_s
else
# expect non-updated attributes to not have changed:
expect(patient_after[attribute_name]).to eq patient_before[attribute_name]
end
end
end
It solves the problem of comparing floating point numbers by converting the values to it string representation using JSON.
It also solves the problem of checking that the new values have been updated but the rest of the attributes have not changed.
In my experience, though, as the complexity grows, the usual thing to do is to check some specific object state instead of "expecting that the attributes I don't update won't change". Imagine, for instance, having some other attributes changing as the update is done in the controller, like "remaining items", "some status attributes"... You would like to check the specific expected changes, that may be more than the updated attributes.
Here is my way of testing PUT. That is a snippet from my notes_controller_spec, the main idea should be clear (tell me if not):
RSpec.describe NotesController, :type => :controller do
let(:note) { FactoryGirl.create(:note) }
let(:valid_note_params) { FactoryGirl.attributes_for(:note) }
let(:request_params) { {} }
...
describe "PUT 'update'" do
subject { put 'update', request_params }
before(:each) { request_params[:id] = note.id }
context 'with valid note params' do
before(:each) { request_params[:note] = valid_note_params }
it 'updates the note in database' do
expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)
end
end
end
end
Instead of FactoryGirl.build(:company).attributes.symbolize_keys, I'd write FactoryGirl.attributes_for(:company). It is shorter and contains only parameters that you specified in your factory.
Unfortunately that is all I can say about your questions.
P.S. Though if you lay BigDecimal equality check on database layer by writing in style like
expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)
this may work for you.
Testing the rails application with rspec-rails gem.
Created the scaffold of user.
Now you need to pass all the examples for the user_controller_spec.rb
This has already written by the scaffold generator. Just implement
let(:valid_attributes){ hash_of_your_attributes} .. like below
let(:valid_attributes) {{ first_name: "Virender", last_name: "Sehwag", gender: "Male"}
}
Now will pass many examples from this file.
For invalid_attributes be sure to add the validations on any of field and
let(:invalid_attributes) {{first_name: "br"}
}
In the users model .. validation for first_name is as =>
validates :first_name, length: {minimum: 5}, allow_blank: true
Now all the examples created by the generators will pass for this controller_spec
I'm trying to make some simple model test:
/app/models/album.rb:
class Album < ActiveRecord::Base
has_many :slides, dependent: :restrict_with_exception
validates :name, presence: true
end
/spec/model/album_spec.rb:
require 'spec_helper'
describe Album do
before do
#album = Album.new(name: 'Example Album')
end
describe "when album name is already taken" do
before do
another_album = #album.dup
another_album.save
end
it { should_not be_valid }
end
end
I was expecting it to fail first (as I have no validates :uniqueness and index on the name field) but it passed. So I changed:
it { should_not be_valid }
to
it { should be_valid }
To see what's going on and this is what I got:
1) Album when album name is already taken should be valid
Failure/Error: it { should be_valid }
expected #<Album id: nil, name: nil, created_at: nil, updated_at: nil> to be valid, but got errors: Name can't be blank
# ./spec/models/album_spec.rb:14:in `block (3 levels) in <top (required)>'
I would like to ask you what I did wrong.
One more thing is if I can/should use expect rather than should syntax here ? I read somewhere that should is a bit deprecated and not expect is recomended but I don't know how to use it for model testing (I have it on my Controller/View test in form of expect(page) or expect(current_path). What argument can I use for model ?
I have never seen the it syntax that you are using. First thing, I would checkout the quick start documentation available here: https://github.com/rspec/rspec-rails#model-specs and then make sure that you are familiar with this set of docs as well: http://rspec.info/
From the example on github:
require "spec_helper"
describe User do
it "orders by last name" do
lindeman = User.create!(first_name: "Andy", last_name: "Lindeman")
chelimsky = User.create!(first_name: "David", last_name: "Chelimsky")
expect(User.ordered_by_last_name).to eq([chelimsky, lindeman])
end
end
You would want to change your second describe to an it and then use one or more expect to determine if the test passes. it takes a string that appears in the test output. So generally you want to make it something expressive. Additionally there is no need to use before blocks here. You can do everything in the it block:
require 'spec_helper'
describe Album do
it "fails validation when album name is already taken" do
album = Album.new(name: 'Example Album')
another_album = album.dup
expect {another_album.save!}.to raise_error(ActiveRecord::RecordInvalid,'Validation failed: This question is no longer active.')
end
end
Setup an explicit subject before your example:
subject {#album}
it { should_not be_valid }
Currently, as per the failure error#<Album id: nil, name: nil, created_at: nil, updated_at: nil> an implicit blank instance of Album is created as no explicit subject is found before the example.
I'm using RSpec (2.10.1) to test validations on a model and have extracted some code to share with other model validations. The validations were first written on the Companies table, so the code looks like this:
# support/shared_examples.rb
shared_examples "a text field" do |field, fill, length|
it "it should be long enough" do
#company.send("#{field}=", fill * length)
#company.should be_valid
end
etc...
end
and the usage is:
# company_spec.rb
describe Company do
before { #company = Company.new( init stuff here ) }
describe "when address2" do
it_behaves_like "a text field", "address2", "a", Company.address2.limit
end
etc...
end
I'd like to pass the #company as a parameter to the shared example so I can reuse the code for different models, something like this:
# support/shared_examples.rb
shared_examples "a text field" do |model, field, fill, length|
it "it should be long enough" do
model.send("#{field}=", fill * length)
model.should be_valid
end
etc...
end
and the usage is:
# company_spec.rb
describe Company do
before { #company = Company.new( init stuff here ) }
describe "when address2" do
it_behaves_like "a text field", #company, "address2", "a", Company.address2.limit
end
etc...
end
However, when I do this I get undefined method 'address2' for nil:NilClass. It appears #company is not being passed (not in scope?) How do I get something like this to work?
The problem is that self within the example group is different from self within a before hook, so it's not the same instance variable even though it has the same name.
I recommend you use let for cases like these:
# support/shared_examples.rb
shared_examples "a text field" do |field, fill, length|
it "it should be long enough" do
model.send("#{field}=", fill * length)
model.should be_valid
end
end
# company_spec.rb
describe Company do
describe "when address2" do
it_behaves_like "a text field", "address2", "a", Company.address2.limit do
let(:model) { Company.new( init stuff here ) }
end
end
end