Rails 3.1, RSpec - failing but page loads fine - ruby-on-rails

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.

Related

Rspec mysteriously passes when testing XML or CSV output

In my Rails 5 app I have this:
class InvoicesController < ApplicationController
def index
#invoices = current_account.invoices
respond_to do |format|
format.csv do
invoices_file(:csv)
end
format.xml do
invoices_file(:xml)
end
end
end
private
def invoices_file(type)
headers['Content-Disposition'] = "inline; filename=\"invoices.#{type.to_s}\""
end
end
describe InvoicesController, :type => :controller do
it "renders a csv attachment" do
get :index, :params => {:format => :csv}
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(response).to render_template :index
end
end
My problem is that my Spec always passes (!), even when I put a bunch of crap into my index.csv.erb file. It seems that the view file isn't even evaluated / tested by RSpec.
How is this possible? What am I missing here?
Controller tests/specs are these weird stubbed creations born out of the idea of unit testing controllers in isolation. That idea turned out to be pretty flawed and has really fallen out of vogue lately.
Controller specs don't actually make a real HTTP request to your application that passes through the routes. Rather they just kind of fake it and pass a fake request through.
To make the tests faster they also don't really render the views either. Thats why it does not error out as you have expected. And the response is not really a real rack response object either.
You can make RSpec render the views with render_views.
describe InvoicesController, :type => :controller do
render_views
it "renders a csv attachment" do
get :index, format: :csv
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(response).to render_template :index
end
end
But a better and more future proof option is using a request spec.
The official recommendation of the Rails team and the RSpec core team
is to write request specs instead. Request specs allow you to focus on
a single controller action, but unlike controller tests involve the
router, the middleware stack, and both rack requests and responses.
This adds realism to the test that you are writing, and helps avoid
many of the issues that are common in controller specs.
http://rspec.info/blog/2016/07/rspec-3-5-has-been-released/
# spec/requests/invoices
require 'rails_helper'
require 'csv'
RSpec.describe "Invoices", type: :request do
let(:csv) { response.body.parse_csv }
# Group by the route
describe "GET /invoices" do
it "renders a csv attachment" do
get invoices_path, format: :csv
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(csv).to eq ["foo", "bar"] # just an example
end
end
end
The format option should be specified outside of the params, i.e. get :index, params: {}, format: :csv}.
Regarding RSpec evaluating views, no, in controller tests, it doesn't, regardless of the format. However, it's possible to test views with RSpec: https://relishapp.com/rspec/rspec-rails/v/2-0/docs/view-specs/view-spec

Testing Controllers in Rails 4

I am looking for clarification and an understanding on how to effectively test my controllers with Rspec, I don't want to write tests that are not testing the potential issues at hand.
My scenario is as follows.
I am using Active Admin to create a Category, to do so you must obviously be logged into Active Admin.
What I want to ensure is that
1) A logged in user can create a Category
2) A Category cannot be created if you are not logged in
3) Attempts to create a Category outside of active admin are met with a 404 template
So what i have so far (and i really want to check i haven't gone over the top or performing unnecessary tests) is as follows.
spec/controllers/categories_controller_spec.rb
require 'rails_helper'
include Warden::Test::Helpers
# Ensure 404 pages are returned when requesting URLS
RSpec.describe CategoriesController, type: :request do
describe 'Routes' do
context 'All CRUD actions render 404' do
it '#create' do
post '/categories'
expect(response.status).to eq(404)
expect(response).to render_template(:file => "#{Rails.root}/public/404.html.erb")
end
# All other actions here
end
end
end
RSpec.describe Admin::CategoriesController, type: :request do
describe 'No Authorised Login' do
context 'All CRUD actions redirect correctly' do
it 'redirects when accessing #index' do
get '/admin/categories'
expect(response.status).to eq(302)
expect(response).to redirect_to(admin_root_path + '/login')
end
# All other actions here
end
end
end
# Ensure actions in admin can be carried out if logged in
RSpec.describe Admin::CategoriesController, type: :request do
before(:each) do
#user = FactoryGirl.create(:admin_user)
login_as #user
end
after(:each) do
#user.destroy
end
describe 'Authorised Login' do
context 'All CRUD actions perform as expected' do
it 'navigates to Categories #index' do
get '/my_admin_panel/categories'
expect(response.status).to eq(200)
expect(response).to render_template(:index)
end
# All other actions here
end
end
spec/routing/categories_routing.spec
RSpec.describe CategoriesController, type: :routing do
describe 'Routes' do
it 'does not get #index' do
expect(get: '/categories').to route_to(
controller: 'application',
action: 'raise_not_found',
unmatched_route: 'categories'
)
end
end
end
Should I be testing post /categories without supplying params, is that a wasted test? Am I over complicating what should be a simple set of tests ?
This is a judgement/style question and, as such, is not ideal for the StackOverflow format. That said, I don't think your testing is over the top. Some other thoughts:
Some people choose treat their controller tests as integration tests.
You can take advantage of RSpec's shared examples to DRY up your tests
In the default RSpec configuration, the database will be cleaned after each test, so you don't need to explicitly destroy the ActiveRecord objects you create if it's the database you're worried about
In general, I think testing behavior for programmatic actions you don't expect to happen (e.g. posting to undefined routes) is worthwhile unless you are trying to test specific error handling code

login_as with rspec fails first test

Following instructions from that Devise How-To page I'm trying to rebuild all my rspec test to bypass user signin process.
There are 2 methods that can be used for that:
sign_in from Devise - which can't be used with feature tests (Capybara)
login_as from Warden (Devise is build on top of it)
1st method at the first shot worked (all test passes) except those with Capybara so I decided to leave it for now.
2nd gives me some weird results - all passes except the first one (any which I place as the first in the file). It fails when I run just one of them. I've checked it with binding.pry and it fails as the user is not logged in and it redirects to the login page. Somehow first test triggers something that makes all the rest passing. I have no clue what's going on here.
I was using around hook before but it behaves really weird so I've changed that to set of before and after (at the same time it works much faster as it creates just one user at the begging rather than for every test). This is how it looks like now:
require 'spec_helper'
describe AlbumsController do
let(:album) { create(:album) }
before(:all) do
#user = create :user
end
before(:each) do
login_and_switch_schema #user
end
after(:all) do
destroy_users_schema #user
destroy_user #user
end
describe "GET #new" do
before { get :new }
it { expect(response).to render_template :new }
end
describe "GET #edit" do
before { get :edit, id: album }
it { expect(response).to render_template :edit }
end
...
and I've defined that helpers:
Warden.test_mode!
def login_and_switch_schema(user)
##request.env["devise.mapping"] = Devise.mappings[:user]
#sign_in :user, user
login_as(user, scope: :user)
Apartment::Database.switch(user.username)
end
def destroy_users_schema(user)
Apartment::Database.drop(user.username)
Apartment::Database.reset
end
def destroy_user(user)
User.destroy(user)
end
I would like to ask you for help.
I would try moving your before(:all) and after(:all) code into the before(:each) and after(:each). :all doesn't play well with let, DatabaseCleaner or in giving you a predictable order of execution for the first test executed.

Optimize Rails RSpec Tests

I'm working on a test for my Rails 4 app and I'm pretty new to using RSpec. I have a controller named AppsController which has the standard index, new, show, create... methods and they all work the way Rails suggest Etc. "new" creates a new instance of the object and create actually saves it, show, shows it and index shows all of the object. Here are my current tests can anyone see any potential problems or things that i could improve?
FactoryGirl.define do
factory :developer do
email 'example#me.com'
password 'new_york'
password_confirmation 'new_york'
tos '1'
end
factory :app do
name 'New App'
tos '1'
end
factory :invalid_app, parent: :app do
name 'nil'
tos '0'
end
end
require 'spec_helper'
def create_valid!
post :create, app: app_attributes
end
def create_invalid!
post :create, app: app_invalid_attributes
end
def show!
get :show, id: app
end
def update_valid!
put :update, id: app, app: app_attributes
end
def update_invalid!
put :update, id: app, app: app_invalid_attributes
end
def delete!
delete :destroy, id: app
end
def http_success
expect(response).to be_success
end
def expect_template(view)
expect(response).to render_template(view)
end
describe AppsController do
render_views
before(:each) do
#developer = FactoryGirl.create(:developer)
#developer.confirm!
sign_in #developer
end
let(:app) { FactoryGirl.create(:app, developer: #developer) }
let(:app_attributes) { FactoryGirl.attributes_for(:app) }
let(:app_invalid_attributes) { FactoryGirl.attributes_for(:invalid_app) }
describe 'GET #index' do
it 'responds with an HTTP 200 status' do
get :index
http_success
end
it 'renders the :index view' do
get :index
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
app = FactoryGirl.create(:app, :developer => #developer)
get :index
expect(assigns(:app)).to eq([app])
end
end
describe 'POST #create' do
context 'with valid parameters' do
it 'creates a new app' do
expect { create_valid!
}.to change(App, :count).by(1)
end
it 'redirects to the new app keys' do
create_valid!
expect(response).to redirect_to keys_app_path(App.last)
end
end
context 'with invalid parameters' do
it 'does not create the new app' do
expect { create_invalid!
}.to_not change(App, :count)
end
it 'renders the :new view' do
create_invalid!
expect_template(:new)
end
end
end
describe 'GET #show' do
it 'responds with an HTTP 200 status' do
show!
http_success
end
it 'renders the :show view' do
show!
expect_template(:show)
end
it 'populates #app with the requested app' do
show!
expect(assigns(:app)).to eq(app)
end
end
describe 'PUT #update' do
context 'with valid parameters' do
it 'locates the requested app' do
update_valid!
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
update_valid!
expect(app.name).to eq('Updated App')
end
it 'redirects to the updated app' do
update_valid!
expect(response).to redirect_to app
end
end
context 'with invalid parameters' do
it 'locates the requested app' do
update_invalid!
expect(assigns(:app)).to eq(app)
end
it 'does not change app attributes' do
update_invalid!
expect(app.name).to_not eq('Updated App')
end
it 'renders the :edit view' do
update_invalid!
expect_template(:edit)
end
end
end
describe 'DELETE #destroy' do
it 'deletes the app' do
expect { delete!
}.to change(App, :count).by(-1)
end
it 'redirects to apps#index' do
delete!
expect(response).to redirect_to apps_url
end
end
end
count should have been changed by -1, but was changed by 0 - on DELETE #destroy
expecting <"new"> but rendering with <[]> - on POST #create
expected: "Updated App"
got: "New App" - on PUT #update
expecting <"edit"> but rendering with <[]> - on PUT #update
expected: [#<App id: nil, unique_id: "rOIc5p", developer_id: 18, name: "New App">]
got: nil - on GET #index
Small thing - your #http_success method is testing the exact same thing twice.
You could also factor out the references to app by putting a #let statement right after your #before block:
let(:app) { FactoryGirl.create(:app, developer: #developer) }
then in your specs, just
it 'renders the :show view' do
get :show, id: app
expect_template(:show)
end
Edit:
Then the order of operations will be 1) the #developer is created in the #before block, 2) the spec is entered, 3) at the first reference to app in the spec, the #let block will create an instance of an app.
That means you can't factor out the app creation in the #index spec, because in that case the spec will call the action before it creates the app.
A few things I thought of, reading your code:
You don't need to include parens on method calls taking no arguments. Just http_success will work.
You should get try to use the modern RSpec expectation syntax consistently. Instead of assigns(:app).should eq(app), use expect(assigns(:app)).to eq(app). (There's one exception to this, which is expectations on mocks (ie. should_receive(:message)), which will only take on the modern expect-to syntax as of RSpec 3.
For controller specs, I like to create little methods for each action that actually invokes the action. You'll notice you call get :show, id: app several times in the GET #show specs. To DRY up your specs a little more, you could instead write the following method within the describe block:
def show!
get :show, id: app
end
Try to use one Hash syntax consistently. What's more, Rails 4 can't be run with Ruby 1.8, so there's (nearly) no reason to use the hash-rocket Hash syntax.
If I'm getting really, really picky, I usually consider instance variables in a spec to be a smell. In almost all cases, instance variables should be refactored to a memoized let/given blocks.
If I'm getting really, really, really picky, I prefer to think of controller specs such as yours as strictly a unit test of the controller, not an integration test (that's what Capybara is for), and as such you shouldn't be exercising your model layer at all. You should only be testing that your controller is sending the right messages to the model layer. In other words, all the model layer stuff should be stubbed out. For example:
describe 'GET #show' do
let(:app) { stub(:app) }
before do
App.stub(:find).and_return(app)
end
it 'populates #app' do
get :show, id: app
assigns(:app).should eq(app)
end
end
I know this last is a personal preference, not a metaphysical truth or even necessarily a wide-spread standard convention, so you may choose to take it or leave it. I prefer it, because it keeps my specs very speedy, and gives me a very clear heuristic for when my controller actions are doing too much, and I might need to consider refactoring. It could be a good habit to get into.
First, I'm not certain but I suspect your invalid app factory may be wrong. Did you mean
factory :invalid_app, parent: :app do
name nil
tos '0'
end
nil as a ruby NilClass not "nil" as a string?
As for other comments about cleanup and stuff, here are a few of my thoughts.
You can avoid the need for some of your helper methods and duplication by using before blocks for each describe. Taking just your index tests you could have something more like
describe 'GET #index' do
before do
get :index
end
it 'responds with an HTTP 200 status' do
http_success
end
it 'renders the :index view' do
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
expect(assigns(:app)).to eq([app])
end
end
Notice also that you don't need to recreate app because your let is doing it as necessary.
On the failures, I suspect the delete count change could be failing because inside the expectation, the test framework is creating a new app (from the let) and then deleting it leading to a count change of 0. For that test, you need to make sure you're app is created outside of your expectation. Because you are using let, you could do that like this:
describe 'DELETE #destroy' do
it 'deletes the app' do
# ensure that app is already created
app
expect {
delete!
}.to change(App, :count).by(-1)
end
end
alternatively, change the let to a let! which will force the creation before the specs actually run.
As for other failures, thought #DanielWright suggested the helper methods, I find those complicate the debug. I can't see where you set the app name to "Updated App", for example. Perhaps a clearer test (for that particular one) would not use the helper methods but could be more explicit. Something like
describe 'PUT #update' do
let(:app_attributes) { FactoryGirl.attributes_for(:app, name: 'The New App Name') }
before do
put :update, id: app, app: app_attributes
end
context 'with valid parameters' do
it 'locates the requested app' do
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
# notice the reload which will make sure you refetch this from the db
expect(app.reload.name).to eq('The New App Name')
end
it 'redirects to the updated app' do
expect(response).to redirect_to app
end
end
end
For the other errors, you might want to start debugging your code. Are you certain it should work? Have you looked at output logs? Maybe the tests are doing there job and finding errors in your controller code. Have you done any step-through debugging?

RSpec controller test fails - how to load mock data?

I have 2 simple RSpec tests i've written for a small rails app i've done to learn rails. I originally had a mock setup for my Link class but was getting the same issue.
This is my test code:
require 'spec_helper'
describe LinksController do
render_views
before(:each) do
link = Link.new
link.stub!(:title).and_return("Reddit")
link.stub!(:url).and_return("http://www.reddit.com")
link.stub!(:created_at).and_return(Time.now)
link.stub!(:updated_at).and_return(Time.now)
link.stub!(:user_id).and_return("1")
link.stub!(:id).and_return("1")
link.save
user = User.new
user.save
end
it "renders the index view" do
get :index
response.should render_template('links/index')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
it "renders the show view" do
get :show, :id => 1
response.should render_template('links/show')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
end
I'm new to both rails and RSpec, not sure what I should be doing to get this to work. What is the best way to test this show method from my LinksController when you need data? I tried mock_model too but maybe I was using it wrong.
You can see all the app code on Github
The problem is that you are stubbing a model, so it's not stored in the database. That means that when you call get :show, :id => 1 the query to the database returns nothing and your tests fail. Stubbing is great when you want to fake a response or an object without using the database, but if you are depending on actual Rails code that uses the database you can't use this method because nothing exists in the database. To fix this I would drop the stubbed models entirely and actually create them.
require 'spec_helper'
describe LinksController do
render_views
before(:each) do
user = User.create
#link = Link.create :title => "Reddit",
:url => "http://www.reddit.com",
:user => user
end
it "renders the index view" do
get :index
response.should render_template('links/index')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
it "renders the show view" do
get :show, :id => #link.id
response.should render_template('links/show')
response.should render_template('shared/_nav')
response.should render_template('layouts/application')
end
end
You should also eventually look into Factory gems like Sham and Factory Girl to simplify the creation of test data.

Resources