I have this rspec code:
let(:valid_attributes) {
{name: "Sample Product"}
}
describe "#index" do
it "should give a collection of products" do
product = Product.create! valid_attributes
get :index, :format => :json
expect(response.status).to eq(200)
expect(response).to render_template("api/products/index")
expect(assigns(:products)).to eq([product])
end
end
And it controller:
def index
#products = Product.all
end
But the controller code still doesn't satisfy the spec. What is wrong here.
Here is the failure message:
Failures:
1) Api::ProductsController#index should give a collection of
products
Failure/Error: expect(assigns(:products)).to eq([product])
expected: [#<Product id: 3, name: "Sample Product", created_at: "2016-06-15 05:10:50", updated_at: "2016-06-15 05:10:50">]
got: nil
(compared using ==)
# ./spec/controllers/api/products_controller_spec.rb:53:in `block (3 levels) in <top (required)>'
Finished in 0.05106 seconds (files took 1.89 seconds to load) 1
example, 1 failure
You have:
get :index, :format => :json
I assume you should have:
get :index, :format => :html
By default, rails returns html, and you didn't specify otherwise in your index action.
assign checks that an instance variable was set
and products should be created in database (use let!(with bang) for it), then:
in your controller:
def index
#products = Product.all # `#products` should be present
end
in rspec:
let(:valid_attributes) {{ name: 'Sample Product' }}
let!(:product) { Product.create!(valid_attributes) } # use `!` here
describe "#index" do
before { get :index, format :json }
it 'should give a collection of products' do
expect(assigns(:products)).to eq([product])
end
end
Related
I was writing tests for my app using responders gem.
Here are my routes:
resources :sites do
resources :pages, shallow: true
end
My PagesController chunk of code:
def create
respond_with(#page = #site.pages.create(page_params))
end
def find_site
#site = current_user.sites.find(params[:site_id])
end
And tests that are failing:
sign_in_user
let(:user_2) { create(:user) }
let(:site) { create(:site, user: #user) }
let(:page) { create(:page, site: site, user: #user) }
describe 'POST #create' do
context 'with valid attributes' do
it 'associates new page with the site' do
expect { post :create, params: { page: attributes_for(:page), site_id: site } }.to change(site.pages, :count).by(1)
end
it 'redirects to show view' do
post :create, params: { page: attributes_for(:page), site_id: site }
expect(response).to redirect_to page_path(assigns(:page))
end
end
Errors are following:
1) PagesController POST #create with valid attributes associates new page with the site
Failure/Error: expect { post :create, params: { page: attributes_for(:page), site_id: site } }.to change(site.pages, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/pages_controller_spec.rb:37:in `block (4 levels) in <top (required)>'
2) PagesController POST #create with valid attributes redirects to show view
Failure/Error: expect(response).to redirect_to page_path(assigns(:page))
ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"pages", :id=>nil}, missing required keys: [:id]
# ./spec/controllers/pages_controller_spec.rb:42:in `block (4 levels) in <top (required)>'
If I change site.pages in first test to Page - it's actually working.
So I am really confused how to fix this tests and where is the mistake.
Solved
Problem was with my PagesController, method create should look like this
def create
#page = #site.pages.build(page_params)
#page.user = current_user
#page.save
respond_with(#page)
end
Problem was with my PagesController, method create should look like this
def create
#page = #site.pages.build(page_params)
#page.user = current_user
#page.save
respond_with(#page)
end
I'm trying to test the ArticlesController in my Rails applications. All of the routes that do not accept params are passing. But all of the routes that expect an id param are failing.
Failures:
1) ArticlesController should find article by id
Failure/Error: get :info, id: #article[:id]
ActionController::UrlGenerationError:
No route matches {:action=>"info", :controller=>"articles", :id=>"60"}
# ./spec/controllers/articles_controller_spec.rb:26:in `block (2 levels) in <top (required)>'
2) ArticlesController should export folder
Failure/Error: get :export_folder, id: #article[:id]
ActionController::UrlGenerationError:
No route matches {:action=>"export_folder", :controller=>"articles", :id=>"60"}
# ./spec/controllers/articles_controller_spec.rb:56:in `block (2 levels) in <top (required)>'
3) ArticlesController should export an article by id
Failure/Error: get :export, id: #article[:id]
ActionController::UrlGenerationError:
No route matches {:action=>"export", :controller=>"articles", :id=>"60"}
# ./spec/controllers/articles_controller_spec.rb:50:in `block (2 levels) in <top (required)>'
config/routes.rb
get '/articles/list' => 'articles#list', defaults: { format: :html }
get '/articles/trendlist' => 'articles#trendlist', defaults: { format: :html }
get '/articles/show/:id' => 'articles#show', defaults: { format: :html }, as: :show_article
get '/articles/index'
get '/articles/info/:id' => 'articles#info', as: :article_info
get '/articles/export/:id' => 'articles#export', as: :export_article
get '/articles/view/:id' => 'articles#view'
get '/articles/favorite/:id' => 'articles#favorite'
get '/articles/trending' => 'articles#trending', defaults: { format: :json }
get '/articles/deleted' => 'articles#deleted', defaults: { format: :json }
get '/articles/csv/:id' => 'articles#csv'
get '/articles/export_folder/:id' => 'articles#export_folder', as: :export_folder
spec/controllers/articles_controller.rb
require 'spec_helper'
describe ArticlesController do
before(:all) do
Article.destroy_all
Comfy::Cms::Layout.destroy_all
Comfy::Cms::Site.destroy_all
site = FactoryGirl.create(:cms_site)
layout = FactoryGirl.create(:cms_layout, site_id: site[:id])
#article = FactoryGirl.create(:cms_page, layout_id: layout[:id], site_id: site[:id])
end
it 'should index articles' do
get :index
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
its 'should list articles' do
get :list
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/html; charset=utf-8')
end
it 'should find article by id' do
get :info, id: #article[:id]
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
it 'should list deleted articles' do
get :deleted
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
it 'should list trending articles' do
get :trending
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
it 'should update trending articles' do
get :trendlist
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/html; charset=utf-8')
end
it 'should export an article by id' do
get :export, id: #article[:id]
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/csv; charset=utf-8')
end
it 'should export folder' do
get :export_folder, id: #article[:id]
response.response_code.should eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/html; charset=utf-8')
end
end
rake routes
Prefix Verb URI Pattern Controller#Action
tags GET /tags(.:format) tags#index
articles_list GET /articles/list(.:format) articles#list
articles_trendlist GET /articles/trendlist(.:format) articles#trendlist
articles GET /articles/show/:id(.:format) articles/articles#show
articles_index GET /articles/index(.:format) articles#index
GET /articles/info/:id(.:format) articles/articles#info
GET /articles/export/:id(.:format) articles/articles#export
GET /articles/view/:id(.:format) articles/articles#view
GET /articles/favorite/:id(.:format) articles/articles#favorite
articles_trending GET /articles/trending(.:format) articles#trending
articles_deleted GET /articles/deleted(.:format) articles#deleted
GET /articles/csv/:id(.:format) articles/articles#csv
GET /articles/export_folder/:id(.:format) articles/articles#export_folder
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
include ArticlesHelper
before_action :set_default_response_format, except: [:pdf, :show, :list, :trendlist, :export_folder]
def index
#articles = SearchArticlesCommand.new(params).execute
end
def deleted
#dlist = Article.deleted.map(&:article_id)
render :ids, status: :ok
end
def info
id = params[:id].to_i
#article = Article.published.find_by(id: id)
end
def list
#articles = Article.folder
render 'articles/list'
end
def favorite
...
render json: { result: true, is_liked: "#{is_liked}" }
end
def view
...
render json: { result: true }
end
def trending
load_trending_articles
end
def trendlist
load_trending_articles
render 'articles/trendlist'
end
def export
id = params[:id].to_i
#article = Article.published.find_by(id: id)
render pdf: #article.label.gsub(/\s/, '_'),
template: 'articles/export.pdf.erb',
dispostion: 'attachment',
locals: { paragraphs: #article.paragraphs, images: #article.images }
That is not really what namespace is used for. You can read up more on it here. Use resources instead and specify member for the one with id:
resources :articles, only: [] do
collection do
get :list
get :trendlist
get :trending
get :deleted
end
member do
get :info
get :export
get :view
get :favorite
get :csv
get :export_folder
end
end
get 'articles/index', to: 'articles#index'
get 'articles/show/:id', to: 'articles#show'
If you look at the output of rake routes you can see that Rails is looking for articles/articles#show etc. namespace is for creating routes which live in a namespace (duh) such as /admin/tools which would root to Admin::ToolsController.
You can instead use scope which adds a url prefix but not the namespace or resources:
resources :articles, only: [:show, :index] do
member do
get 'info'
get 'export' # Use /articles/1.format instead.
get 'view' # Do you need this? Code smell!
get 'favorite' # should be post - GET should never create or alter a resource.
get 'csv' # remove - use /articles/1.csv instead
get 'show' # /articles/show/3
end
collection do
get 'trending'
get 'deleted'
get 'trendlist'
get 'list' # Do you need this? Code smell!
get 'index' # /articles/index
end
end
I would also question why you actually need so many routes beyond the standard CRUD set.
Especially routes which are extremely in semantics like view and show and list and index.
I would use query parameters around a smaller set of routes as it reduces the amount of duplication on all levels.
/articles?filter=deleted => index
/articles?filter=trending
Rails also has a built in CSV mime type so you can do:
/articles/5.csv
class ProductsController < ApplicationController
def show
#article = Article.order(:name)
respond_to do |format|
format.html
format.csv { render text: #article.to_csv }
end
end
end
Using #article[:id] vs #article.id does work but its unidiomatic and very slightly slower since rails has to go through the [] method just to find the getter method. Its not a huge deal in this case but not great when you are dealing with a large number of objects.
I'm trying to write a simple RSpec test for "UsersController" that tests the index method.
The code for the controller index method looks as follows:
# GET /users
# GET /users.json
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users }
end
end
I am trying to test that the "all" method is called and that the index view is rendered. Here is my Rspec code for that:
require 'spec_helper'
describe UsersController do
describe 'get index', :type => :controller do
before :each do
#fake_users = [double('user1'), double('user2')]
end
it 'should call the model method that retrieves all Users' do
User.should_receive(:all).once.and_return(#fake_users)
get :index
end
describe 'after valid search' do
before :each do
User.stub(:all).and_return(#fake_users)
get :index
end
it 'should select the index template for rendering' do
response.should render_template('index')
end
it 'should make the users results available to that template' do
assigns(:users).should == #fake_users
end
end
end
end
However, this fails the "get index" test with the following message:
Failure/Error: User.should_receive(:all).once.and_return(#fake_users)
(<User(id: integer, firstname: string, lastname: string, username: string, email: string, password_digest: string, created_at: datetime, updated_at: datetime) (class)>).all(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/controllers/users_controller_spec.rb:9:in `block (3 levels) in <top (required)>'
Can anyone shed any light on what I'm doing wrong?
The problem was caused by the UsersController requiring a log in first. So the controller looked like:
class UsersController < AuthenticatedController
So the path wasn't accessible to RSpec.
I'm fairly new to Rails and I'm trying to get all my specs to pass. However, one of the default specs fails with the following error:
Failures:
1) RatingsController GET index assigns all ratings as #ratings
Failure/Error: expect(assigns(:ratings)).to eq([rating])
expected: [#<Rating id: 1, rottentomatoes_id: 1, rater_id: 1, rating: 10, created_at: "2014-06-17 22:58:53", updated_at: "2014-06-17 22:58:53">]
got: nil
(compared using ==)
# ./spec/controllers/ratings_controller_spec.rb:34:in `block (3 levels) in <top (required)>'
Finished in 1 minute 34.34 seconds (files took 4.55 seconds to load)
61 examples, 1 failure
Failed examples:
rspec ./spec/controllers/ratings_controller_spec.rb:30 # RatingsController GET index assigns all ratings as #ratings
Here's the spec (it's a default controller spec generated by bundle exec rails generate scaffold ...):
RSpec.describe RatingsController, :type => :controller do
include_context 'shared context'
describe "GET index" do
it "assigns all ratings as #ratings" do
rating = Rating.create! valid_attributes
get :index, {}, valid_session
expect(assigns(:ratings)).to eq([rating])
end
end
end
The shared_context:
RSpec.shared_context 'shared context' do
include Devise::TestHelpers
include Warden::Test::Helpers
Warden.test_mode!
let(:user1) { User.find_by(id: 1) || FactoryGirl.create(:user1) }
let(:user2) { User.find_by(id: 2) || FactoryGirl.create(:user2) }
let(:user3) { User.find_by(id: 3) || FactoryGirl.create(:user3) }
let(:user4) { User.find_by(id: 4) || FactoryGirl.create(:user4) }
let(:admin) { Admin.find_by(id: 1) || FactoryGirl.create(:admin) }
before do
#admin = Admin.find_by(id: 1) || FactoryGirl.create(:admin)
login_as(#admin, scope: :admin)
end
end
I should add that I'm using Devise in conjunction with the controller:
class RatingsController < ApplicationController
before_action :set_rating, only: [:show, :edit, :update, :destroy]
before_action :authenticate_admin!
# GET /ratings
# GET /ratings.json
def index
#ratings = Rating.all
end
end
I finally figured things out using this link: https://github.com/plataformatec/devise/wiki/How-To:-Controllers-tests-with-Rails-3-(and-rspec)
The document states that it is for Rails 3, but I find that it also works fine for Rails 4.
i have used devise in rspec testing. this is my test
describe BooksController do
before(:all) do
#user = FactoryGirl.create(:user)
end
describe "GET index" do
it "shows list of current user books" do
sign_in #user
book = #user.books.create!(:title => "user")
get :index, {}
assigns(:books).should eq(#user.books)
end
end
describe "GET show" do
it "assigns the requested book as #book" do
sign_in #user
book = #user.books.create!(:title => "user")
visit_count = book.visits.to_i
get :show, {:id => book.to_param}
assigns(:book).should eq(book)
book = Book.find(book.id)
visit_count.should_not eq(book.visits)
end
end
describe "GET new" do
it "assigns a new book as #book" do
sign_in #user
get :new, {}
assigns(:book).should be_a_new(Book)
end
end
end
factory
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "foo#{n}#example.com" }
password '12345678'
password_confirmation '12345678'
confirmed_at Time.now
end
end
book controller
class BooksController < ApplicationController
before_action :authenticate_user!, only: [:index, :edit, :update, :destroy, :new, :my_books, :add_wish_list]
# GET /books
# GET /books.json
def index
#books = current_user.books
end
# GET /books/1
# GET /books/1.json
def show
#book = Book.find(params[:id])
#book.book_visit_count
if(session["warden.user.user.key"].present?)
#book.book_visit_user(session["warden.user.user.key"][0][0])
end
end
# GET /books/new
def new
#book = Book.new
end
end
error
Failure/Error: assigns(:book).should be_a_new(Book)
expected nil to be a new Book(id: integer, title: string, author: string, isbn_10: string, isbn_13: string, edition: integer, print: integer, publication_year: integer, publication_month: string, condition: string, value: integer, status: boolean, stage: integer, description: text, visits: integer, user_id: integer, prefered_place: string, prefered_time: string, created_at: datetime, updated_at: datetime, rating: integer, image: string, publisher: string, goodreads_id: string)
# ./spec/controllers/books_controller_spec.rb:66:in `block (3 levels) in <top (required)>'
the problem is the third test "get new" fails when i run the test as a whole but passes when i run it individually. and also if i remove the before_authenticate! in controller then all test passes.
Again if i commented out the "assigns" in first two describe blocks then all tests pass again.
i am using rails 4.0.2 and rspec 2.14.7 , devise 3.2.2
The only thing I can figure is that your authenticate_user method is failing for users that have previously been authenticated. It's not affecting show because you don't have :show listed in your before_action. You could test this theory by requiring authentication for show as well and seeing if your second example starts failing for before(:all) as well.