Rspec rails on rails validation - ruby-on-rails

validate :cannot_modify_if_locked, on: :update
def cannot_modify_if_locked
if self.locked
errors.add(:locked_at, "#{self.locked_at} and cannot be modified")
false
end
end
RSPEC
consent_form = build(:consent_form, { locked_at: Date.today })
expect(consent_form.).to eq true
This test is not good.
how to write this test? i need to check with rspec this validation on update

Within your spec you can use the let helper to memoize an object, which you could use to "recreate" an update which wouldn't have success, that's to say, must be invalid.
Suppose you have your model, you can create a record which will have their attributes and also the locked as true, and the locked_at attribute to show the date in which it was locked:
RSpec.describe SomeModel, type: :model do
let(:some_object) { SomeModel.new(locked: true, locked_at: Time.now) }
Then you can access the object "created" with let and try to perform an update to it, then you can use expect to validate it won't be valid:
it 'invalid if object is locked' do
some_object.update(some_atttribute: 'Some new attribute')
expect(some_object).to_not be_valid
end
And also you could check for the validation error message:
expect(some_object.errors[:locked_at].first).to eq("#{task.locked_at} and cannot be modified")

Related

Why does create from factory_girl throw error?

When i don't use factory_girl
I can create objects in my rspec test as
u = User.create(email: 'as#wewe.com', password: 'asdqweqweqe', admin: true, firstname: 'qwe', lastname: 'wer', grade: 5, section: 'w')
expect(u.errors[:email]).to_not be_empty
expect(u.errors[:role]).to_not be_empty
This way i can check for validation error i.e
expect(u.errors[:role]).to_not be_empty
but if i use factory girl
factory :user_no_role, class: User do
email 'asd#we.com'
password 'asasdasdasd'
admin true
firstname 'qwe'
lastname 'wer'
grade 5
section 'w'
end
it 'role should be present' do
u = create(:user_no_role)
expect(u.errors[:role]).to_not be_empty
end
I get the following error
User role should be present
Failure/Error: u = create(:user_no_role)
ActiveRecord::RecordInvalid:
Validation failed: Role can't be blank
does factory_girl create method throw error? if so how can i test for validation error with rspecs as done above? Thanks!
Create will throw validation exceptions, since you are trying to save them immediately.
If you would just build one, like:
u = User.new(...)
u.valid?
=> false
u.errors # is not empty
This should apply to FactoryGirl.build too
it 'role should be present' do
u = build(:user_no_role)
expect(u.errors[:role]).to_not be_empty
end
BTW: User.create will not throw exceptions - but User.create! does.
See the ActiveRecord docs of create! and FactoryGirl docs of build for more infos.

FactoryGirl + Rspec custom validation test

I'm pretty new to testing.
I have a custom validation at my Profile model
def birth_date_cannot_be_in_the_future
errors.add(:birth_date, "the birth date cannot be in the future") if
!birth_date.blank? && birth_date > Date.today
end
At my factories.rb
sequence(:email) {|n| "person-#{n}#example.com"}
factory :user do
email
password 'password'
password_confirmation 'password'
confirmed_at Time.now
end
factory :profile do
user
first_name { "User" }
last_name { "Tester" }
birth_date { 21.years.ago }
end
At my models/profile_spec.rb
it 'birth date cannot be in the future' do
profile = FactoryGirl.create(:profile, birth_date: 100.days.from_now)
expect(profile.errors[:birth_date]).to include("the birth date cannot be in the future")
expect(profile.valid?).to be_falsy
end
When I run my test I receive the follow message:
Failure/Error: profile = FactoryGirl.create(:profile, birth_date: 100.days.from_now)
ActiveRecord::RecordInvalid:
The validation fails: Birth date the birth date cannot be in the future
What am I doing wrong?
There's a matcher just for catching errors. Without having tested, I'm assuming you can go:
expect { FactoryGirl.create(:profile, birth_date: 100.days.from_now) }.to raise_error(ActiveRecord::RecordInvalid)
Another approach though is to include the shoulda gem, which has the allow_value matcher. This lets you do something more like this in your model spec:
describe Profile, type: :model do
describe 'Birth date validations' do
it { should allow_value(100.years.ago).for(:birth_date) }
it { should_not allow_value(100.days.from_now).for(:birth_date) }
end
end
Generally you wont need FactoryGirl at all when you're testing things like validations. They become super useful in controller tests though.
Just put assertions for your model in a model spec, which tests your model code directly. These usually live in spec/models/model_name_spec.rb There are convenient shoulda matchers for a bunch of common model stuff:
describe SomeModel, type: :model do
it { should belong_to(:user) }
it { should have_many(:things).dependent(:destroy) }
it { should validate_presence_of(:type) }
it { should validate_length_of(:name).is_at_most(256) }
it { should validate_uniqueness_of(:code).allow_nil }
end
It should be using build instead of create
Also profile.valid? should be called before checking profile.errors
it 'birth date cannot be in the future' do
profile = FactoryGirl.build(:profile, birth_date: 100.days.from_now)
expect(profile.valid?).to be_falsy
expect(profile.errors[:birth_date]).to include("the birth date cannot be in the future")
end

How to complete the rspec put controller test from scaffold

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

Rails + Rspec: How to stub a record so that it's marked as invalid?

I have a Purchase model with a method:
def set_status_to_in_progress!
self.update_attributes!(status: IN_PROGRESS)
end
And a failing rspec test:
context "self is invalid" do
it "raises an error" do
purchase = Purchase.new
purchase.stub(:valid?).and_return(:false)
expect { purchase.set_status_to_in_progress! }.to raise_error
end
end
which returns
Failures:
1) Purchase#set_status_to_in_progress! self is invalid raises an error
Failure/Error: expect { purchase.set_status_to_in_progress! }.to raise_error
expected Exception but nothing was raised
# ./spec/models/purchase_spec.rb:149:in `block (4 levels) in <top (required)>'
I thought stubbing valid? would be enough to make the ActiveRecord update_attributes! method raise an error? How would I make it raise?
Try changing :false to false
purchase.stub(:valid?).and_return(false)
or
purchase.should_receive(:valid?).and_return(false)
otherwise you can stub any instance of Purchase
Purchase.any_instance.should_receive(:valid?).and_return(false)
This is the DEFINITIVE guide to successfully test validation errors when there is no way to simulate with real validations on your model. In my case the SupportRequest model does not have any validation but I want to test like there is one, so what I did first was create a double then make it so it return false when trying to update, then added errors to the record and last test that the records were there. :)
describe "with invalid data" do
before do
the_double = instance_double("SupportRequest", id: support_request.id)
active_model_errors = ActiveModel::Errors.new(the_double).tap { |e| e.add(:description, "can't be blank") }
allow_any_instance_of(SupportRequest).to receive(:update_attributes).and_return(false)
allow_any_instance_of(SupportRequest).to receive(:errors).and_return(active_model_errors)
put "/api/support_requests/#{support_request.id}",
params: {
data: {
type: "support-requests",
attributes: {}
}
},
headers: authenticated_header(support_agent)
end
it "should not create a new support_request" do
expect_json(errors: [
{
source: {
pointer: "/data/attributes/description"
},
detail: "can't be blank"
}
])
end
it "should return status code (422)" do
expect_status(422)
end
end

Rails model.valid? flushing custom errors and falsely returning true

I am trying to add a custom error to an instance of my User model, but when I call valid? it is wiping the custom errors and returning true.
[99] pry(main)> u.email = "test#test.com"
"test#test.com"
[100] pry(main)> u.status = 1
1
[101] pry(main)> u.valid?
true
[102] pry(main)> u.errors.add(:status, "must be YES or NO")
[
[0] "must be YES or NO"
]
[103] pry(main)> u.errors
#<ActiveModel::Errors:[...]#messages={:status=>["must be YES or NO"]}>
[104] pry(main)> u.valid?
true
[105] pry(main)> u.errors
#<ActiveModel::Errors:[...]#messages={}>
If I use the validate method from within the model, then it works, but this specific validation is being added from within a different method (which requires params to be passed):
User
def do_something_with(arg1, arg2)
errors.add(:field, "etc") if arg1 != arg2
end
Because of the above, user.valid? is returning true even when that error is added to the instance.
In ActiveModel, valid? is defined as following:
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
run_validations!
ensure
self.validation_context = current_context
end
So existing errors are cleared is expected. You have to put all your custom validations into some validate callbacks. Like this:
validate :check_status
def check_status
errors.add(:status, "must be YES or NO") unless ['YES', 'NO'].include?(status)
end
If you want to force your model to show the errors you could do something as dirty as this:
your_object = YourModel.new
your_object.add(:your_field, "your message")
your_object.define_singleton_method(:valid?) { false }
# later on...
your_object.valid?
# => false
your_object.errors
# => {:your_field =>["your message"]}
The define_singleton_method method can override the .valid? behaviour.
This is not a replacement for using the provided validations/framework. However, in some exceptional scenarios, you want to gracefully return an errd model. I would only use this when other alternatives aren't possible. One of the few scenarios I have had to use this approach is inside of a service object creating a model where some portion of the create fails (like resolving a dependent entity). It doesn't make sense for our domain model to be responsible for this type of validation, so we don't store it there (which is why the service object is doing the creation in the first place). However for simplicity of the API design it can be convenient to hang a domain error like 'associated entity foo not found' and return via the normal rails 422/unprocessible entity flow.
class ModelWithErrors
def self.new(*errors)
Module.new do
define_method(:valid?) { false }
define_method(:invalid?) { true }
define_method(:errors) do
errors.each_slice(2).with_object(ActiveModel::Errors.new(self)) do |(name, message), errs|
errs.add(name, message)
end
end
end
end
end
Use as some_instance.extend(ModelWithErrors.new(:name, "is gibberish", :height, "is nonsense")
create new concerns
app/models/concerns/static_error.rb
module StaticError
extend ActiveSupport::Concern
included do
validate :check_static_errors
end
def add_static_error(*args)
#static_errors = [] if #static_errors.nil?
#static_errors << args
true
end
def clear_static_error
#static_errors = nil
end
private
def check_static_errors
#static_errors&.each do |error|
errors.add(*error)
end
end
end
include the model
class Model < ApplicationRecord
include StaticError
end
model = Model.new
model.add_static_error(:base, "STATIC ERROR")
model.valid? #=> false
model.errors.messages #=> {:base=>["STATIC ERROR"]}
A clean way to achieve your needs is contexts, but if you want a quick fix, do:
#in your model
attr_accessor :with_foo_validation
validate :foo_validation, if: :with_foo_validation
def foo_validation
#code
end
#where you need it
your_object.with_foo_validation = true
your_object.valid?

Resources