Rails & Shoulda Spec Matcher: validate_presence_of (:first_name) blowing up - ruby-on-rails

describe User do
it { should belong_to(:shop) }
it { should respond_to(:shop) }
it { should respond_to (:first_name)}
it { should respond_to (:last_name)}
it { should respond_to (:email)}
Works fine. But as soon as I add:
it { should validate_presence_of (:first_name)}
it { should validate_presence_of (:last_name)}
It breaks, specifically in this callback method:
def get_url
source = "http://www.randomsite.com/"+ first_name + "+" + last_name
end
no implicit conversion of nil into String for +
How do I fix this?

Looks like your first_name and last_name are nil for the test.
Try to stub the values in a before block like this:
context 'first_name and last_name validation' do
before do
allow(subject).to receive(:first_name).and_return('bob')
allow(subject).to receive(:last_name).and_return('tom')
end
it { should validate_presence_of (:first_name)}
it { should validate_presence_of (:last_name)}
end

Well, this is how validate_presence_of(:first_name) works:
It assigns nil to your model's first_name attribute and calls .valid? on your model.
If .valid? returns true - the test fails, since if you have had correct validation in place, that wouldn't happen.
I assume that get_url method is set up to run on before_validation callback, so what is going on in your code:
first_name gets assigned to nil.
.valid? is called.
before_validation callback fires your get_url method
Everything blows up, because get_url doesn't get into account that first_name can be nil.

Related

How to stub zendesk_api current_user for a spec?

I have a model method which I am trying to write a spec for. The method is like this:
def my_method
puts current_user.user_attirbute
end
Where current_user is provided by an authentication gem, zendesk_api-1.14.4. To make this method testable, I changed it to this:
def my_method(user_attribute = nil)
if user_attribute = nil
user_attribute = current_user.user_attribute
end
puts user_attribute
end
This refactor works and is testable, but doesn't seem like a good practice. Ideally the gem would provide some sort of test helper to help stub/mock the current_user, but I haven't been able to find anything. Any suggestions?
You can go simple way and just test returning of proper value by current_user#user_attribute method. Example:
describe '#my_method' do
subject { instance.my_method } # instance is an instance of your class where #my_method is defined
let(:user) { instance_spy(ZendeskAPI::User, user_attribute: attr) }
let(:attr} { 'some-value' }
before do
allow(instance).to receive(:current_user).and_return(user)
end
it { is_expected.to eq(attr) }
end
But I would go with VCR cassette(vcr gem is here) because it is related 3rd party API response - to minimize a risk of false positive result. Next example demonstrates testing with recorded response(only in case if #current_user method performs a request to zendesk):
describe '#my_method', vcr: { cassette_name: 'zendesk_current_user' } do
subject { instance.my_method }
it { is_expected.to eq(user_attribute_value) } # You can find the value of user_attribute_value in recorded cassette
end
P.S. I assumed that you put puts in your method for debugging. If it is intentional and it is part of the logic - replace eq with output in my example.

Rails 5 validation on update fires on create too

I am using factory bot gem to build my factory objects for rspec test. And I have a validation like this in my model :
validates :reason_for_change, presence: true, on: :update
The spec test fails because the object itself cannot be created :
Failure/Error: let(:user) { create(:super_user) }
ActiveRecord::RecordInvalid:
Validation failed: Profile reason for change can't be blank
I have also tried this:
validates :reason_for_change, presence: true, unless: :new_record?
Same error. This as well :
validates :reason_for_change, presence: true, if: :my_custom_method
def my_custom_method
!new_record?
end
Same result. However when I used the last validation and put a breakpoint inside my custom method like this :
def my_custom_method
binding.pry
end
And was manually first checking is this a new_record?, whenever it would evaluate to true, I would return false (manually typing). And for every false evaluated, I'd return true (manually typing).
In this case all tests pass. What is going on here? I am using rails 5.0.1
Update 1:
Here is my super user factory :
factory :super_user do
transient do
email { nil }
end
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
gender { 'male' }
age { Random.rand(18..40) }
ethnicity { '' }
avatar { Rack::Test::UploadedFile.new(Rails.root.join('spec/files/1.png')) }
end
Also I don't think I should modify factory, just to make this work. This should work.
A copy-paste of my comment:
Please check the logs. I believe, you will see there something like this:
INSERT INTO super_users () VALUES(...)
UPDATE super_users SET filed = val
If so - please check another callbacks that may be run when record is saved. My guess is that you have another callback that updates same record in, for example, after_save callback.
You should disable that validation first before trying my suggestion.

FactoryGirl attribute set in after(:create) doesnt persist until referenced?

Sorry for the vague title, there are a lot of moving parts to this problem so I think it will only be clear after seeing my code. I'm fairly sure I know what's going on here and am looking for feedback on how to do it differently:
I have a User model that sets a uuid attr via an ActiveRecord callback (this is actually in a "SetsUuid" concern, but the effect is this):
class User < ActiveRecord::Base
before_validation :set_uuid, on: :create
validates :uuid, presence: true, uniqueness: true
private
def set_uuid
self.uuid = SecureRandom.uuid
end
end
I am writing a functional rspec controller test for a "foo/add_user" endpoint. The controller code looks like this (there's some other stuff like error-handling and #foo and #params being set by filters, but you get the point. I know this is all working.)
class FoosController < ApplicationController
def add_user
#foo.users << User.find_by_uuid!(#params[:user_id])
render json: {
status: 'awesome controller great job'
}
end
end
I am writing a functional rspec controller test for the case "foo/add_user adds user to foo". My test looks roughly this (again, leaving stuff out here, but the point should be obvious, and I know it's all working as intended. Also, just to preempt the comments: no, I'm not actually 'hardcoding' the "user-uuid" string value in the test, this is just for the example)
RSpec.describe FoosController, type: :controller do
describe '#add_user' do
it_behaves_like 'has #foo' do
it_behaves_like 'has #params', {user_id: 'user-uuid'} do
context 'user with uuid exists' do
let(:user) { create(:user_with_uuid, uuid: params[:user_id]) } # params is set by the 'has #params' shared_context
it 'adds user with uuid to #foo' do
route.call() # route is defined by a previous let that I truncated from this example code
expect(foo.users).to include(user) # foo is set by the 'has #foo' shared_context
end
end
end
end
end
end
And here is my user factory (I've tried setting the uuid in several different ways, but my problem (that I go into below) is always the same. I think this way (with traits) is the most elegant, so that's what I'm putting here):
FactoryGirl.define do
factory :user do
email { |n| "user-#{n}#example.com" }
first_name 'john'
last_name 'naglick'
phone '718-555-1234'
trait :with_uuid do
after(:create) do |user, eval|
user.update!(uuid: eval.uuid)
end
end
factory :user_with_uuid, traits: [:with_uuid]
end
end
Finally, The problem:
This only works if I reference user.uuid before route.call() in the spec.
As in, if I simply add the line "user.uuid" before route.call(), everything works as intended.
If I don't have that line, the spec fails because the user's uuid doesn't actually get updated by the after(:create) callback in the trait in the factory, and thus the User.find_by_uuid! line in the controller does not find the user.
And just to preempt another comment: I'm NOT asking how to re-write this spec so that it works like I want. I already know a myriad of ways to do this (the easiest and most obvious being to manually update user.uuid in the spec itself and forget about setting the uuid in the factory altogether). The thing I'm asking here is why is factorygirl behaving like this?
I know it has something to do with lazy-attributes (obvious by the fact it magically works if I have a line evaluating user.uuid), but why? And, even better: is there some way I can do what I want here (setting the uuid in the factory) and have everything work like I intend? I think it's a rather elegant looking use of rspec/factorygirl, so I'd really like it to work like this.
Thanks for reading my long question! Very much appreciate any insight
Your issue has less to do with FactoryGirl and more to do with let being lazily evaluated.
From the docs:
Use let to define a memoized helper method. The value will be cached across
multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.
Since your test doesn't invoke the user object until the expectation there is nothing created. To force rspec to load object, you can use let!.
Instead of using the before_validation callback you should be using after_initialize. That way the callback is fired even before .valid? is called in the model lifecycle.
class User < ActiveRecord::Base
before_initialization :set_uuid!, on: :create, if: :set_uuid?
validates :uuid, presence: true, uniqueness: true
private
def set_uuid!
# we should also check that the UUID
# does not actually previously exist in the DB
begin
self.uuid = SecureRandom.uuid
end while User.where(uuid: self.uuid).any?
end
def set_uuid?
self.uuid.nil?
end
end
Although the chance of generating the same hash twice with SecureRandom.uuid is extremely slim it is possible due to the pigeonhole principle. If you maxed out in the bad luck lottery this would simply generate a new UUID.
Since the callback fires before validation occurs the actual logic here should be completely self contained in the model. Therefore there is no need to setup a callback in FactoryGirl.
Instead you would setup your spec like so:
let!(:user) { create(:user) }
it 'adds user with uuid to #foo' do
post :add_user, user_id: user.uuid, { baz: 3 }
end

how to test rspec shoulda for custom validation in rails?

I have a Private methods in my model like the following:
validate :record_uniq
private
def record_uniq
if record_already_exists?
errors.add(:base, "already exists")
end
end
def record_already_exists?
question_id = measure.question_id
self.class.joins(:measure).
where(measures: {question_id: ques_id}).
where(package_id: pack_id).
exists?
end
This methods is more like a uniqueness scope thing to prevent duplicate records. I want to know how to write test for validate :record_uniq by using shoulda or rspec?
Example of what i tried:
describe Foo do
before do
#bar = Foo.new(enr_rds_measure_id: 1, enr_rds_package_id: 2)
end
subject { #bar }
it { should validate_uniqueness_of(:record_uniq) }
end
Simple - build an object that fails the validation, validate it, and verify that the correct error message has been set.
For example (if you had a model named City):
it 'validates that city is unique' do
city = City.new # add in stuff to make sure it will trip your validation
city.valid?
city.should have(1).error_on(:base) # or
city.errors(:base).should eq ["already exists"]
end
Here's what I would do using RSpec 3 syntax.
it 'validates that city is unique' do
city = City.new('already taken name')
expect(city).to be_invalid
expect(city.errors[:base]).to include('already exists')
end

Rails association.create and association.create! returns nil

I'm just throwing this out there because I really can't figure this out. When I call for instance user.articles.create! { title: 'blah' } nil is returned but the object is created. I've not seen anything like this before and was wondering if someone else has?
I've tried rails 3.2.13 and 3.2.12 and they both do the same thing.
EDIT
In active record both create and create! ends up IN THIS METHOD that is supposed to return the record or throw an exception.
def create_record(attributes, options, raise = false, &block)
unless owner.persisted?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
if attributes.is_a?(Array)
attributes.collect { |attr| create_record(attr, options, raise, &block) }
else
transaction do
add_to_target(build_record(attributes, options)) do |record|
yield(record) if block_given?
insert_record(record, true, raise)
end
end
end
end
If I'm not mistaken Factory Girl mimic the actual object you're dealing with through your predefined factory. Therefor User#articles might not return what you think it is when called on a factory.
Changing
user.articles.create! { title: 'blah' }
to
create(:article, user: user, title: 'blah')
should enforce the association through Factory Girl's interface.
I believe there is something going on with your attr_accessible or attr_accessor in your Article class. I you might have not included the user_id or something else...
There is also a similar question here: rails Model.create(:attr=>"value") returns model with uninitialized fields
I had the same symptom, and this question is the only relevant hit that I could find. I'll throw my solution into the mix in case it helps anyone else.
The code worked in real life, and only failed under rspec. All the troubleshooting I did made no sense, pointing to create! being broken, which I never believed.
As it turns out, I was mocking create! so it never got called. Adding .and_call_original to my mock solved the problem.
My model was something like this: (not really...but compatible with this answer)
class Flight < ApplicationRecord
has_many :seats
def create_seats(seat_count)
seat_count.times { Seat.create!(flight: self) }
seats.each(&:raise_seatback_and_lock_tray)
end
And my test was:
it 'creates enough empty seats' do
expect(LicenseFile).to receive(:create!).twice
flight.create_seats(2)
end
The expectation was met (confirmed manually), but an error was raised:
NoMethodError:
undefined method `raise_seatback_and_lock_tray=' for nil:NilClass
Changing my mock to allow create! to actually be called solved the problem:
it 'creates a LicenseFile for each destination rule' do
expect(LicenseFile).to receive(:create!).twice.and_call_original
flight.create_seats(2)
end
This now passed:
creates enough empty seats
1 example, 0 failures
If you are expecting the object to be returned use
user.articles.create { title: 'blah' }
Why some methods have bang (!), you can read this topic
Why are exclamation marks used in Ruby methods?

Resources