Rspec organization - ruby-on-rails

I am currently using rspec with cancan. I realized that littering permission control test cases all over my controllers spec files is extremely messy. Basically, almost every controller spec files of mine has something along the lines:
describe "failure" do
it { get :new }
it { get :edit, :id => #deal }
it { get :update, :id => #deal }
it { get :destroy, :id => #deal }
after(:each) do
response.should_not be_success
response.should redirect_to(root_path)
flash[:error].should == "Permission denied."
end
end
end
I have 4 roles in my system and this definitely makes organization a much more difficult task.
Since all of these tests are related to permission control/ACL, I tried putting them all in one file rspec/models/ability_spec.rb
right now, my ability_spec looks like this:
describe "cancan" do
it "failure" do
#ability.should_not be_able_to(:all, Factory(:purchase))
#ability.should_not be_able_to(:all, Factory(:user))
#ability.should_not be_able_to(:all, Visit)
end
end
I am getting the following error:
6) Ability consumers deals failure
Failure/Error: it { get :destroy, :id => #deal }
NoMethodError:
undefined method `get' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1::Nested_2::Nested_2:0x007fd73209a270>
# ./spec/models/ability_spec.rb:46:in `block (5 levels) in <top (required)>'
I know I am not supposed to put controller get/post in this file. Is there a way to do this for the sake of simplifying testing for my permission related tests?

Take a look at RSpec's shared examples and see if you can pull anything out into a shared example group:
http://relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples

Related

Rails 4 automatically generated RSpec test for delete is failing, even while the feature works in development

This is the automatically generated test for the destroy action in Rails 4, as part of the spec for vehicles_controller.rb:
describe "DELETE destroy" do
it "destroys the requested vehicle" do
vehicle = Vehicle.create! valid_attributes
expect {
delete :destroy, {:id => vehicle.to_param}, valid_session
}.to change(Vehicle, :count).by(-1)
end
it "redirects to the vehicles list" do
vehicle = Vehicle.create! valid_attributes
delete :destroy, {:id => vehicle.to_param}, valid_session
response.should redirect_to(vehicles_url)
end
end
and here is what I've got in the controller, again very standard:
def destroy
#vehicle = Vehicle.find(params[:id])
#vehicle.destroy
flash[:notice] = "Vehicle has been deleted"
redirect_to vehicles_url
end
This works just fine in the app itself -- when you delete a vehicle, it redirects back to the vehicles_url, and the entry is deleted from the database. The server log looks completely normal as well. However, when I run the specs, they fail as follows:
1) VehiclesController DELETE destroy destroys the requested vehicle
Failure/Error: expect {
count should have been changed by -1, but was changed by 0
# ./spec/controllers/vehicles_controller_spec.rb:148:in `block (3 levels) in <top (required)>'
2) VehiclesController DELETE destroy redirects to the vehicles list
Failure/Error: response.should redirect_to(vehicles_url)
Expected response to be a redirect to <http://test.host/vehicles> but was a redirect to <http://test.host/>.
Expected "http://test.host/vehicles" to be === "http://test.host/".
# ./spec/controllers/vehicles_controller_spec.rb:156:in `block (3 levels) in <top (required)>'
Can anyone point me to what might be going on here to make the test fail? Thanks for any help!
Edit: here is some additional information about before filters that could be affecting things. In the vehicles_controller, since I am using the gem CanCan, I have load_and_authorize_resource at the top. This controller is also being tested for the ability to create and update, and those specs are passing, so I assumed that was not interfering, plus it wasn't failing with any messages to do with permissions. Maybe I need to make a change to the default let(:valid_session) { {} } at the top of the controller spec? I was leaving that alone because, as I said, it was fine for all the other actions besides delete.
Further edit:
In light of a link provided below, I edited my spec to be :
describe "DELETE destroy" do
it "destroys the requested vehicle" do
vehicle = Vehicle.create! valid_attributes
expect {
delete :destroy, :id => vehicle.to_param, valid_session
}.to change(Vehicle, :count).by(-1)
end
it "redirects to the vehicles list" do
vehicle = Vehicle.create! valid_attributes
delete :destroy, :id => vehicle.to_param, valid_session
response.should redirect_to(vehicles_url)
end
end
Now, if I try to run the specs, I receive this syntax error:
/home/kathryn/testing/spec/controllers/vehicles_controller_spec.rb:150: syntax error, unexpected '\n', expecting => (SyntaxError)
Line 150 refers to the line in the first spec that begins with delete :destroy, where the change was made.
You are most likely encountering an authorization error. Try adding flash[:alert].should == nil after your delete operation in either test. If you are having an authorization problem, you'll get something like "You are not authorized to access this page." in your test failures.
The biggest clue to this is that your tests are also failing with:
Expected response to be a redirect to <http://test.host/vehicles> but was a
redirect to <http://test.host/>.
A typical implementation of authorization failure redirects to the root path.
I'm personally not a fan of the , valid_session approach to handling sign-in. If you're using Devise, you should use their test helpers and sign in appropriately. E.g.
describe VehiclesController do
include Devise::TestHelpers
before(:each) do
sign_in FactoryGirl.create(:user)
end
. . .
Then (and this is important!) remove all the , valid_session parameters, otherwise they'll override the session that devise has set up for you with the sign_in method.
That's the simplest form; this will still fail if some users can (e.g.) delete and others can't, so for those cases you might want to create a separate describe "Admin users can" do ... end block of tests, and have a before(:each) which calls sign_in with an admin user.
I hope that helps, and best of luck!
Ruby's parser gets confused with inline hashes. You probably want:
delete :destroy, {:id => vehicle.to_param}, valid_session
You're getting in trouble with the passing of your arguments to delete. See the method definition at http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-delete.
You can use either:
delete :destroy, {id: vehicle.to_param, session: valid_session}
or
delete :destroy, id: vehicle:to_param, session: valid_session
I'm not exactly sure what's happening inside of ActionController with the last two parameters you're passing in, but it can't be right.

ActiveRecord reporting a failed validation, but there are no validations defined

I'm just starting to use RSpec with a new project (after being a minitest user for a while). I've created a single MVC, called contracts. Here is the model file:
class Contract < ActiveRecord::Base
attr_accessible :name, :number, :plannedStart, :actualStart, :plannedCompletion, :actualCompletion
end
I've got a basic factory defined for Contracts (it was slightly more complex earlier, using sequence to generate novel names and numbers, but I removed all of that to try to simplify squashing this bug):
FactoryGirl.define do
factory :contract do
end
end
Here are the specs (pretty much auto generated, except I added in the calls to the factory):
require File.dirname(__FILE__) + '/../spec_helper'
describe ContractsController do
render_views
it "index action should render index template" do
create(:contract)
get :index
response.should render_template(:index)
end
it "show action should render show template" do
create(:contract)
get :show, :id => Contract.first
response.should render_template(:show)
end
it "new action should render new template" do
get :new
response.should render_template(:new)
end
it "create action should render new template when model is invalid" do
Contract.any_instance.stubs(:valid?).returns(false)
post :create
response.should render_template(:new)
end
it "create action should redirect when model is valid" do
Contract.any_instance.stubs(:valid?).returns(true)
post :create
response.should redirect_to(contract_url(assigns[:contract]))
end
it "edit action should render edit template" do
create(:contract)
get :edit, :id => Contract.first
response.should render_template(:edit)
end
it "update action should render edit template when model is invalid" do
create(:contract)
Contract.any_instance.stubs(:valid?).returns(false)
put :update, :id => Contract.first
response.should render_template(:edit)
end
it "update action should redirect when model is valid" do
create(:contract)
create(:contract)
Contract.any_instance.stubs(:valid?).returns(true)
put :update, :id => Contract.first
response.should redirect_to(contract_url(assigns[:contract]))
end
it "destroy action should destroy model and redirect to index action" do
create(:contract)
contract = Contract.first
delete :destroy, :id => contract
response.should redirect_to(contracts_url)
Contract.exists?(contract.id).should be_false
end
end
When I run the spec, I get the following error message, but the number of these I get varies from run to run:
ActiveRecord::RecordInvalid: Validation failed:
./spec/controllers/contracts_controller_spec.rb:39:in `block (2 levels) in <top (required)>'
ActiveRecord::RecordInvalid: Validation failed:
./spec/controllers/contracts_controller_spec.rb:45:in `block (2 levels) in <top (required)>'
ActiveRecord::RecordInvalid: Validation failed:
./spec/controllers/contracts_controller_spec.rb:7:in `block (2 levels) in <top (required)>'
10 examples, 3 failures, 7 passed
Finished in 0.360139 seconds
/Users/jlee/.rvm/rubies/ruby-1.9.3-p194/bin/ruby -S rspec ./spec/controllers/contracts_controller_spec.rb ./spec/models/contract_spec.rb failed
I've implemented Databasecleaner just to make sure this wasn't some odd behavior problem relating to using transactions during testing, but to no avail. Suggestions? I have no validations defined, so its hard to understand how I could be failing validation.
The stub for :valid? hangs around between tests, so I put the following in the before(:each) block: Contract.any_instance.unstub(:valid?), and it cleared everything up.

what is the wrong with this spec and controller code?

I'm trying to test an existing rails project with rspec. And I want to test a controller but getting an error which I can't solve :S
Here is the my spec code ;
require 'spec_helper'
describe BriefNotesController do
before(:all) do
#customer=Factory(:customer)
#project=Factory(:project_started, :owner => #customer)
end
context 'get :new' do
it 'should redirect to login page for not signed in users' do
get :new, :project_id => #project.id
response.should redirect_to("/kullanici-girisi")
end
it 'should be success and render new brief note page for project owner' do
sign_in #customer
get :new, :project_id => #project.id
response.should be_success
end
end
end
Here is the my controller code ;
class BriefNotesController < ApplicationController
before_filter :authenticate_user!
before_filter :find_project
def new
#brief_note = #project.brief_notes.new
end
def create
#brief_note = #project.brief_notes.build(params[:brief_note])
if #brief_note.save
redirect_to brief_project_path(#project)
else
render :action => :new
end
end
private
def find_project
#project = current_user.projects.find_by_cached_slug([params[:project_id]])
end
end
I think current_user.projects.find_by_cached_slug method don't work. So this is the error;
Failures:
1) BriefNotesController get :new should be success and render new brief note page for project owner
Failure/Error: get :new, :project_id => #project.id
NoMethodError:
undefined method `brief_notes' for nil:NilClass
# ./app/controllers/brief_notes_controller.rb:6:in `new'
# ./spec/controllers/brief_notes_controller_spec.rb:19:in `block (3 levels) in <top (required)>'
I can't say for certain without more information about your models, but a likely culprit is that you're passing #project.id as the request parameter, but you're doing the lookup by cached_slug. Try #project.to_param instead.
The error is coming from your find_project filter: find_by_cached_slug is returning a nil, which is assigned to #project, and that triggers the undefined method error when brief_notes is called on it (in the new action).
From your spec description I assume that it shouldn't even be executing the new code, and instead redirecting in authenticate_user!? I don't use devise myself (this is a devise method, right?) so I'm not sure of the specifics of that method, but I think that's where your problem is coming from.
I don't think the problem is your FactoryGirl syntax, which is deprecated but should still work.

Rails 3.1, RSpec - failing but page loads fine

I am writing some specs and the following is failing but the page /menus/1 is loading fine in a browser. This is a port of a php app and is first time I've used RSpec. Any thoughts as to why it might not be working.
The error is:
1) MenusController GET 'show' should be succesful
Failure/Error: get :show, :id => 1
ActiveRecord::RecordNotFound:
Couldn't find MenuHeader with id=1
# ./app/controllers/menus_controller.rb:18:in `show'
# ./spec/controllers/menus_controller_spec.rb:7:in `block (3 levels) in <top (required)>'
but that specific MenuHeader does exist based upon all normal criteria (console, mysql, browser). I'm 99% sure I have a mistake in my spec:
require 'spec_helper'
describe MenusController do
describe "GET 'show'" do
it "should be succesful" do
get :show, :id => 1
response.should be_success
end
end
end
here is the menus_controller.rb
def show
#menu_header_data=MenuHeader.find(params[:id])
respond_to do |format|
format.html # show.html.erb
# format.json { render json: #menu } to do
end
end
thx
When testing a controller with Rspec or TestUnit I would use a Factory or Fixture to pass the id rather than setting up a test database with data. It's better to test with something like:
Using FactoryGirl (My Recommendation but everyone has their own tastes):
describe MenusController do
describe "GET 'show'" do
it "should be succesful" do
get :show, :id => Factory(:menu).id
response.should be_success
end
end
end
The test is mainly just to make sure the controller responds properly when provided valid data, and using Factories or Fixtures is much less brittle. It will become a pain to maintain your test suite if it's based on hard data like fixtures or a db backup, and that could ultimately lead to you giving up on Test Driven Development rather than embracing it.

RSpec Error: Mock "Employee_1" received unexpected message:to_ary with(no args)

My application has two models: User and Employee, and their relation is user has_many employees.
As I trying to write a Rspec test case for the employee controller:
describe "GET 'edit'" do
it "should get user/edit with log in" do
log_in(#user)
employee = mock_model(Employee, :id=>1, :user_id=>#user.id)
get :edit, :id=>employee
response.should be_success
end
end
I got the result as:
....F
Failures:
1) EmployeesController GET 'edit' should get user/edit with log in
Failure/Error: get :edit, :id=>employee
Mock "Employee_1" received unexpected message :to_ary with (no args)
# C:in `find'
# ./app/controllers/employees_controller.rb:41:in `edit'
# ./spec/controllers/employees_controller_spec.rb:51:in `block (3 levels) in '
Finished in 4.31 seconds
5 examples, 1 failure
Can someone help me out with this please? Thanks
I think the problem is you are using mock_model, which will throw errors if you call a method on the model that you are not explicitly creating an expectation for. In this case, you could create you employee with stub_model since you are performing no checks on calls actually made to the model.
describe "GET 'edit'" do
it "should get user/edit with log in" do
log_in(#user)
employee = stub_model(Employee, :id=>1, :user_id=>#user.id)
get :edit, :id=>employee
response.should be_success
end
end
Rails is able to infer the ID from a true model instance, which is how this works:
#employee = Employee.create
get :edit, :id => #employee
This doesn't work with mock_model, for reasons that are unclear to me. You can simply pass in the ID explicitly:
employee = mock_model(Employee, :id => 1, :user_id=>#user.id)
get :edit, :id => employee.id # or just :id => 1
I think the problem is with mock_model, you can use double for that.
describe "GET 'edit'" do
it "should get user/edit with log in" do
log_in(#user)
employee = double('employee', :id=>1, :user_id=>#user.id)
get :edit, :id=>employee
response.should be_success
end
end

Resources