Mocking a non database backed model in Rails with Rspec - ruby-on-rails

I am trying to create a non ActiveRecord model in my Ruby on Rails application according to http://railscasts.com/episodes/121-non-active-record-model. I am facing hard time testing it though.
I have following class in my app/models/sms.rb file.
class Sms
def initialize
# Do stuff here
end
def deliver
# Do some stuff here
end
end
I am unable to mock deliver method on my Sms class.
it 'should do someting' do
#sms = mock(Sms)
#sms.should_receive(:deliver).and_return(true)
post :create, :user_id => #user.id
flash[:notice].should == "SMS sent successfully."
response.should redirect_to(some_url)
end
In my controller code I do have a line that says #sms.deliver. Yet above gives following error:
Failure/Error: #sms.should_receive(:deliver).and_return(true)
(Mock Sms).deliver(any args)
expected: 1 time
received: 0 times
Any pointers?

Variables beginning with # are instance variables. The #sms your controller refers to is not the same #sms as your spec has defined.
Try changing
#sms = mock(Sms)
#sms.should_receive(:deliver).and_return(true)
to
Sms.any_instance.should_receive(:deliver).and_return(true)

If your version of RSpec doesn't have the any_instance method, you'll need to stub :new on the class:
#sms = mock(Sms)
Sms.stub(:new).and_return(#sms)
#sms.should_receive(:deliver).and_return(true)

Related

Passing a named route to a controller macro in RSpec

I'm trying to DRY up my RSpec examples by adding a few controller macros for frequently used tests. In this somewhat simplified example, I created a macro that simply tests whether getting the page results in a direct to another page:
def it_should_redirect(method, path)
it "#{method} should redirect to #{path}" do
get method
response.should redirect_to(path)
end
end
I'm trying to call it like so:
context "new user" do
it_should_redirect 'cancel', account_path
end
When I run the test I get an error saying that it doesn't recognize account_path:
undefined local variable or method `account_path' for ... (NameError)
I tried including Rails.application.routes.url_helpers per the guidance given in this SO thread on named routes in RSpec but still receive the same error.
How can I pass a named route as a parameter to a controller macro?
The url helpers included with config.include Rails.application.routes.url_helpers are valid only within examples (blocks set with it or specify). Within example group (context or describe) you cannot use it. Try to use symbols and send instead, something like
# macro should be defined as class method, use def self.method instead of def method
def self.it_should_redirect(method, path)
it "#{method} should redirect to #{path}" do
get method
response.should redirect_to(send(path))
end
end
context "new user" do
it_should_redirect 'cancel', :account_path
end
Don't forget to include url_helpers to config.
Or call the macro inside example:
def should_redirect(method, path)
get method
response.should redirect_to(path)
end
it { should_redirect 'cancel', account_path }

`flash.discard` in Rails 2.3 fails because flash is a Hash, not a FlashHash

Given a functional test such as:
def test_exciting_rails_upgrades
login(m=users(:manager))
post :import_users_map_fields, :csv_file => fixture_file_upload('/users.csv', 'text/csv')
assert flash.empty?
flash.discard
# other goodies omitted
end
In Rails 2.3.2 there are no errors, however in 2.3.15 the error is:
NoMethodError: undefined method `discard' for {}:Hash
/test/functional/broken_upgrades.rb:119:in `test_exciting_rails_upgrades'
Why is flash a Hash class instead of a FlashHash?
From the source it looks like both 2.3.2 and 2.3.15 ActionPack files lib/action_controller/flash.rb create the FlashHash class and inherit from Hash. However what is shown in this functional test in both 2.3.2 and 2.3.15 is a Hash class, not a HashFlash, so one cannot call discard on it.
Can anyone else reproduce this error with 2.3.15 and flash.discard?
Here are two test cases you can use to prove ActionController changes the type of 'flash' depending on whether or not it is already set.
In my app, you cannot see :index unless you're logged in, so in test_flash_is_now_a_flashhash you see that flash was set by the backend properly, while in test_flash_is_a_plain_hash it was not.
def test_flash_is_a_plain_hash
login(users(:permitted_user))
get :index
assert flash.instance_of?(Hash)
end
def test_flash_is_now_a_flashhash
get :index
assert_redirected_to :controller => "login"
assert flash.instance_of?(ActionController::Flash::FlashHash)
end
You can see this for yourself in the ActionController::TestRequest code:
def flash
session['flash'] || {}
end
Update: This has been fixed in Rails branch 2-3-stable.

Rspec testing instance variables with user creation

I'm testing to make sure that a created user is assigned to my instance variable #user. I understand what get means, but I'm not sure what to write for the test. I'm returning with an argument error for a bad URI or URL. What's wrong with my test and how do I fix it?
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :users
# I'm confused on what this line above means/does. What does the hash :users refer
#to
assigns[:user].should == [p]
end
The expected URI object or string error refers to get :users and the error is as follows
Failure/Error get :users
ArgumentError:
bad argument: (expected URI object or URI string)
I guess that what you want is
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :show, id: p.id
assigns(:user).should == p
end
The line you were not sure about checks that content of the assigned variable (#user) in the show view of the user p, is equal to the p user you just created more information there
what action are you trying to test? usually, for creation, you need to test that the controller's "create" action creates a user and assigns an #user variable
I would test it this way:
describe 'POST create' do
it 'creates a user' do
params = {:user => {:name => 'xxx', :lastname => 'yyy'}}
User.should_receive(:create).with(params)
post :create
end
it 'assigns the user to an #user instance variable' do
user = mock(:user)
User.stub!(:create => user)
post :create
assigns(:user).should == user
end
end
notice that I stub/mock all user methods, since you are testing a controller you don't have to really create the user, you only test that the controller calls the desired method, the user creation is tested inside the User model spec
also, I made 2 tests (you should test only 1 thing on each it block if possible, first it test that the controller creates a user, then I test that the controller assigns the variable
I'm assuming your controller is something like this:
controller...
def create
#user = User.create(params[:user])
end
which is TOO simple, I guess you have more code and you should test that code too (validations, redirects, flash messages, etc)

Set current_user in test

I have a test that looks like this:
test "should get create" do
current_user = FactoryGirl.build(:user, email: 'not_saved_email#example.com')
assert_difference('Inquiry.count') do
post :create, FactoryGirl.build(:inquiry)
end
assert_not_nil assigns(:inquiry)
assert_response :redirect
end
That's testing this part of the controller:
def create
#inquiry = Inquiry.new(params[:inquiry])
#inquiry.user_id = current_user.id
if #inquiry.save
flash[:success] = "Inquiry Saved"
redirect_to root_path
else
render 'new'
end
end
and the factory:
FactoryGirl.define do
factory :inquiry do
product_id 2
description 'I have a question about....'
end
end
but I keep getting errors in my tests:
1) Error:
test_should_get_create(InquiriesControllerTest):
RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
What am I doing wrong? I need to set the current_user, and I believe I am in the test, but obviously, that's not working.
You didn't create current_user. It was initialized only in test block.
There are two differents ways to do it:
First, use devise test helpers. Something like that
let(:curr_user) { FactoryGirl.create(:user, ...attrs...) }
sign_in curr_user
devise doc
Second, you can stub current_user method in your controllers for test env
controller.stub(current_user: FactroryGirl.create(:user, ...attrs...))
And you should use FactoryGirld.create(...) instead of FactoryGirl.build(...), because you factory objects have to be persisted.(be saved in db and has id attribute not nil)
There are several things which come to mind:
FactoryGirl.build(:user, ...) returns unsaved instance of a user. I'd suggest to use Factory.create instead of it, because with unsaved instance there's no id and there's no way for (usually session based) current_user getter to load it from database. If you're using Devise, you should "sign in" user after creating it. This includes saving record in DB and putting reference to it into session. See devise wiki
Also, passing ActiveRecord object to create action like this looks weird to me:
post :create, FactoryGirl.build(:inquiry)
Maybe there's some rails magic in play which recognizes your intent, but I'd suggest doing it explicitly:
post :create, :inquiry => FactoryGirl.build(:inquiry).attributes
or better yet, decouple it from factory (DRY and aesthetic principles in test code differ from application code):
post :create, :inquiry => {product_id: '2', description: 'I have a question about....'}
This references product with id = 2, unless your DB doesn't have FK reference constraints, product instance may need to be present in DB before action fires.

Rspec controller test, trying to create a test for a 'processlogin' action

My create user method in the users_controller.rb looks like:
def process_login
is_login_valid(params[:user][:user_name], params[:user][:password])
if logged_in?
redirect_to root_url
else
#user = User.new(params[:user][:user_name]
redirect_to :action => 'login'
end
What I have currently:
describe UsersController do
describe "Post 'process_login'"
it "should be successful" do
post 'process_login'
response.should be_success
end
end
end
The methods is_login_valid and logged_in? are all included in the application_controller, and are methods from a ruby class I have in my /lib folder called LoginSystem.rb
My test is failing since it isn't mocking things correctly, this is my first time doing this so hoping someone can help me out.
Error message:
UsersController POST 'process_login' should be successful
Failure/Error: post 'process_login'
NoMethodError:
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
# ./app/controllers/users_controller.rb:11:in `process_login'
# ./spec/controllers/users_controller_spec.rb:21
Aah, Thanks for the error message. I'm assuming that line 11 is is_login_valid(params[:user][:user_name], params[:user][:password]).
As you're not sending any params in your test post params[:user] is nil hence the nil.[] error (when the controller is looking for params[:user][:user_name]), you set params by passing them as a hash as the 2nd parameter to post in your test.
I think you actually need
controller.stub(:logged_in?) { true }
Or if you want to test that the logged_in method is actually being called
controller.should_receive(:logged_in?).and_return(true)
The 2nd version will cause the test to fail unless the method logged_in? is called once and only once
You may also need the controller.stub(:is_login_valid} { true } as suggested by #jaydel if you're getting an error message about this method being missing as well
I believe:
controller.stub(:is_login_valid} { true }
should get you where you want to go, if I understand correctly.

Resources