RSpec results and Rails Console behaving differently - ruby-on-rails

Here is my Lesson model:
before_create :set_sequence
def set_sequence
maxseq = Lesson.where(:course_id => self.course_id).maximum("sequence")
if (maxseq.nil?)
maxseq = 0
end
self.sequence = maxseq + 1
end
when I run rspec the following test fails:
it "validate sequence is setup" do
lesson = Lesson.create(:title => "Testing", :description => "Testing", :course_id => 1)
lesson.sequence.should_not eql nil
end
However when T test this through rails console the Lesson object is created successfully and with the correct sequence. Any ideas why?

lesson.sequence.should_not be_nil is the correct way to test for nil, as far as I know. Have you tried that?

Any validations you've got on Lesson could be silently aborting the create before your callback gets called. Change it to create! in the spec to check it.

FactoryGirl first initializes object with no parameters, and then assigns parameters one by one. The callback in your model probably would not work in this case. So you can try to change FactoryGirl's behavior by adding
initialize_with { new(attributes) }
to the Lesson's factory. I'm not sure it will help though. Testing callback behavior in Rails is tricky.

Related

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

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?

How to change an attribute value with rspec

I'm newbie with rspec and I'm facing some problems with it. Could someone help me?
I have a controller action responsible for deactivate an user. I'm trying to cover it with rspec tests, but the result is not what I'm waiting for.
Controller:
def deactivate
#user = User.find(params[:id])
if !#user.nil?
#user.update_attribute(:active, false)
redirect_to users_url
end
end
Controller Spec
describe "PUT #deactivate" do
describe "with valid parameters" do
before (:each) do
#user = mock_model(User, :id => 100, :login => "login", :password => "password123",
:email => "email#gmail.com", :active => true)
User.should_receive(:find).with("100").and_return(#user)
end
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.active.should eq false
end
end
end
The test result:
1) UsersController PUT #deactivate with valid parameters should deactivate an user
Failure/Error: #user.active.should eq false
expected: false
got: true
(compared using ==)
So, I don't understand why the active attribute stills true when it should be false. Any ideas ?
Thanks!
You appear to be stubbing the update_attribute method unnecessarily. Try removing that line and see what happens.
I look for this for a long time, update_column can always work no matter you use let or build
Your expectation is "wrong".
Let's see what happens when your spec it "should deactivate an user" is executed:
#user.stub!(:update_attribute).with(:active, false).and_return(true) modifies the existing mock model, so it has an update_attribute which, when called with arguments :active and false
will return true
will keep track that this call has happened (that's what mocks do)
(and, unlike a real User object, will do nothing else)
put :deactivate, :id => "100" calls the real deactivate in your Controller
Your Controller calls User.find. But you've mocked that class method, which will return the mock object #user instead of searching for the actual user with that id.
Your Controller calls #user.update_attribute. But because of step 3 above, #user here is the mock object, too. Its update_attributes method is the one from step 1. As we've seen above, it will return true, keep track that this call happened and do nothing else. Which means it will not change #user's active attribute, so that stays true.
Changing active when update_attribute is called is functionality of objects of the actual User class, but no such object came into play while running your spec. Because this functionality is inherited from ActiveRecord, you don't have to test it. Instead just test that the update_attribute has been received by the mock object:
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.should have_received(:update_attribute).with(:active, false)
end
(I'm guessing about the old should syntax here, based on how it's done with the newer expect syntax.)
To mock or not?
If you do want to test the combined functionality of your controller with the actual User implementation, do not mock User or its objects. Instead test from the browser perspective with a request spec. (It might make sense to do that additionally, even if you want the isolated tests for only controller (with model mocked) and for only model (which probably won't require doubles, except maybe for other models).
Can you try this:
describe "should deactivate an user" do
before do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
end
it { #user.active.should eq false }
end
when you are mocking the call to update_attribute, how is the model going to change?
if you are a beginner: DONT use stubs and mocks!
first get a general knowledge in testing, THEN expand your knowledge to mocks and stubs.

How should I spec this

The following spec works but I know it shouldn't be like this. I am having a hard time getting my head around rspec, in particular mocks and stubs.
This is the model code
class RecipeFermentable < ActiveRecord::Base
belongs_to :recipe
belongs_to :product
def set_attributes()
attrs = product.product_attributes
self.ppg = attrs.find_by_name(:ppg.to_s).value
self.ecb = attrs.find_by_name(:ecb.to_s).value
end
end
And this is the spec I have written
it "should set the attributes from the product" do
#product_attribute1 = mock_model(ProductAttribute, :name => :ppg, :value => 40)
#product_attribute2 = mock_model(ProductAttribute, :name => :ecb, :value => 1)
#product = Product.new
#product.product_attributes << #product_attribute1
#product.product_attributes << #product_attribute2
#recipe_fermentable = RecipeFermentable.new
#recipe_fermentable.product.should_receive(:product_attributes).and_return(#product_attributes)
#product_attributes.stub(:find_by_name).with(:ppg.to_s).and_return(#product_attribute1)
#product_attributes.stub(:find_by_name).with(:ecb.to_s).and_return(#product_attribute2)
#recipe_fermentable.set_attributes
#recipe_fermentable.ppg.should eql(40)
#recipe_fermentable.ecb.should eql(1)
end
For a start my spec is way bigger than my method, and I am using a real Product. Some pointers on the way to write a goodbetter spec for this would be really helpfull. Also if anyone knows of a good resource for learning rspec using mocks and stubs, please could you add some links.
Thanks
I would change a couple of things here:
Much of the code in your it is just providing context, so it should be in your before(:each) block.
You are setting a message expectation, but it doesn't really seem like you are testing for that. I think the expectation should be switched to a stub instead. Another test could be it 'should call product_attributes', where you would in fact test that expectation - I am not advocating you do this since you would be testing implementation and not behavior, but just making the point.
You are returning #product_attributes in that message expectation, and using it right after to stub the find_by_name calls. However, you never defined #product_attributes. I assume that should be a mock object, and I'm not sure what it really is in that context. Maybe it is nil, and you are stubbing a couple of methods on it.
With those two changes, here's where we are:
before(:each) do
#product = mock_model(Product)
#product_attribute_ppg = mock_model(ProductAttribute, :name => :ppg, :value => 40)
#product_attribute_ecb = mock_model(ProductAttribute, :name => :ecb, :value => 1)
#product_attributes = mock('product_attributes')
#product_attributes.stub!(:find_by_name).with(:ppg.to_s).and_return(#product_attribute_ppg)
#product_attributes.stub!(:find_by_name).with(:ecb.to_s).and_return(#product_attribute_ecb)
#product.stub!(:product_attributes).and_return(#product_attributes)
#recipe_fermentable = RecipeFermentable.new
#recipe_fermentable.stub!(:product).and_return(#product)
end
it 'should set the attributes from the product' do
#recipe_fermentable.set_attributes
#recipe_fermentable.ppg.should eql(40)
#recipe_fermentable.ecb.should eql(1)
end
With all of that all of the way, I don't completely agree with your approach here. I think that you are repeating data and moving away from DB normalization. Unless there's a real reason for that (could be that your way ahead and for performance reasons you had to do this), I would suggest the following instead:
class RecipeFermentable < ActiveRecord::Base
def ppg
#rescue nil here so that if attributes is nil, or find_by_name('ppg') is nil, things don't blow up
product.attributes.find_by_name('ppg').value rescue nil
end
#other
end
A couple of resources for testing:
RSpec book
xUnit patterns: not RSpec nor Ruby, but a must.

Why did post fail in my Rails functional test?

When I run a post in my Rails functional test
setup do
post :create, :user => Factory.attributes_for(:user)
end
and it fails, I don't get any feedback as to why. I know that it fails because my assertion to make sure that there's one additional record in the database fails.
I tried to do
setup do
post :create, :user => Factory.attributes_for(:user)
assert_valid #controller.object
end
but object is a protected method.
How can I examine the errors on the model object that results from the post call?
I'm using Shoulda and Factory Girl, but I suspect that doesn't matter.
Add the following assertion:
assert_nil assigns(:user).errors
Which will fail if there were errors saving your object (perhaps a validation), and show you the value of the errors object.
I'm using rails 3.2.13, and it seems like assert_nil doesn't work properly as stated in the previous answer.
This is what worked for me:
assert_empty assigns(:user).errors
I believe this is because even a successful "save" call returns an ActiveRecord:Errors object containing with an Empty hash of "messages" so you could also do this:
assert_empty assigns(:user).errors.messages

Resources