rspec and friendly_id mismatches - ruby-on-rails

I'm having trouble passing these tests:
# user_controller_spec.rb
describe "GET #edit" do
user = FactoryBot.create(:user)
it "returns http success" do
get :edit, params: {id: user.hash_id}
expect(response).to have_http_status(:success)
end
it "renders :edit" do
get :edit, params: {id: user.hash_id}
expect(response).to render_template :edit
end
end
This outputs:
Failures:
1) UsersController when authenticated GET #edit returns http success
Failure/Error: #user = User.friendly.find(params[:id])
ActiveRecord::RecordNotFound:
can't find record with friendly id: "cWLxnN9DS43g"
# /Users/speasley/.rvm/gems/ruby-2.4.1/gems/friendly_id-5.2.3/lib/friendly_id/finder_methods.rb:23:in `find'
# ./app/controllers/users_controller.rb:109:in `set_user'
# /Users/speasley/.rvm/gems/ruby-2.4.1/gems/rails-controller-testing-1.0.2/lib/rails/controller/testing/template_assertions.rb:61:in `process'
# /Users/speasley/.rvm/gems/ruby-2.4.1/gems/devise-4.4.3/lib/devise/test/controller_helpers.rb:35:in `block in process'
# /Users/speasley/.rvm/gems/ruby-2.4.1/gems/devise-4.4.3/lib/devise/test/controller_helpers.rb:102:in `catch'
# /Users/speasley/.rvm/gems/ruby-2.4.1/gems/devise-4.4.3/lib/devise/test/controller_helpers.rb:102:in `_catch_warden'
# /Users/speasley/.rvm/gems/ruby-2.4.1/gems/devise-4.4.3/lib/devise/test/controller_helpers.rb:35:in `process'
# /Users/speasley/.rvm/gems/ruby-2.4.1/gems/rails-controller-testing-1.0.2/lib/rails/controller/testing/integration.rb:12:in `block (2 levels) in <module:Integration>'
# ./spec/controllers/users_controller_spec.rb:132:in `block (4 levels) in <top (required)>'
But if I puts to compare input/output:
# users_controller.rb
before_action :set_user, only: [:show, :edit, :update, :destroy]
...
def set_user
#user = User.friendly.find(params[:id])
puts "#{params[:id]}, #user.hash_id"
end
I get a different id instead than the one in the failure message. Should these not match? I cannot figure out why the test is failing at all but comparing these seems to be a logical place to start. Within the set_user method, I can puts the found user and their attributes but cannot get the test to pass. Any ideas?

Why are you using hash_id as friendly id? Is this the field where user's friendly id is stored?
There is a method that should return the right friendly id value regardless of which field is actually used for it: friendly_id.
get :edit, params: {id: user.friendly_id}

Related

Rspec testing strong params in Rails not working (wrong number of arguments)

I'm trying to implement some testing for my controller strong params and following the docs to the 'T'.
My controller:
class FutureOutagesController < ApplicationController
before_action :find_future_outage, only: [:show, :update, :destroy]
before_action :future_outage_params, only: [:create, :update, :destroy]
def index
#future_outages = FutureOutage.all
end
def show
end
def create
#future_outage = FutureOutage.new(future_outage_params)
end
def update
end
def destroy
end
private
def find_future_outage
#future_outage = FutureOutage.find(params[:id])
end
def future_outage_params
params.require(:future_outage).permit(:start_time, :end_time, :reason, :service_id)
end
end
my rsepc test with FactoryBot and ShouldaMatchers:
require 'rails_helper'
RSpec.describe FutureOutagesController, type: :controller do
let(:fo) { build :future_outage }
context 'test params' do
it do
params = {
future_outage: {
start_time: fo.start_time,
end_time: fo.end_time,
reason: fo.reason,
service_id: fo.service_id
}
}
should permit(:start_time, :end_time, :reason, :service_id).
for(:create, params: params).on(:future_outage)
end
end
end
I get the error:
Failures:
1) FutureOutagesController test params is expected to (for POST #create) restrict parameters on :future_outage to :start_time, :end_time, :reason, and :service_id
Failure/Error:
should permit(:start_time, :end_time, :reason, :service_id).
for(:create, params: params).on(:future_outage)
ActionView::Template::Error:
wrong number of arguments (given 2, expected 1)
# ./spec/controllers/future_outages_controller_spec.rb:25:in `block (3 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# ArgumentError:
# wrong number of arguments (given 2, expected 1)
# ./spec/controllers/future_outages_controller_spec.rb:25:in `block (3 levels) in <top (required)>'
What am I doing wrong?

RSpec controller test errors within embedded resource

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

Rspec and current user id saved in different model from devise

I'm using Devise to manage users and my goal get the current user to be saved with the created record.
The current user is saved in the controller but my Rspec is wrong!
Thank you all for your help.
My record Model
class Record < ActiveRecord::Base
#Associations
belongs_to :user
# Validations
validates :title, :user, presence: true
end
My record Controller
class RecordsController < ApplicationController
before_action :find_record, only: [:show, :edit, :update, :destroy]
def create
#record = Record.new(record_params)
if #record.save
redirect_to #record
else
#records = Record.all
render 'index'
end
end
def update
if #record.update(record_params)
flash[:notice] = "The record was updated successfully"
redirect_to #record
else
render 'edit'
end
end
private
def find_record
#record = Record.find(params[:id])
end
def record_params
params.require(:record).permit(:title, :description, :user_id).merge(user: current_user) # as suggested
end
end
My Rspec
require 'rails_helper'
describe RecordsController do
let(:record) { create(:record) }
let(:user) { create(:user) }
let(:title) { "Some title I would like to put in my record" }
let(:description) { "description I would like to put in my record" }
describe "#create" do
it "creates a new record with the given title and description" do
expect do
post :create, record: { title: title, description: description, user_id: user }
end.to change { Record.count }.by(1)
expect(response).to redirect_to(assigns[:record])
expect(assigns[:record].title).to eq(title)
expect(assigns[:record].description).to eq(description)
end
it "fails to create a record and returns to the index page" do
expect(post :create, record: { description: description }).to render_template(:index)
expect(assigns[:records]).to eq(Record.all)
end
end
describe "#update" do
it "find the records and sets the new given values" do
put :update, { id: record.id, record: { title: title, description: description } }
record.reload
expect(record.title).to eq(title)
expect(record.description).to eq(description)
expect(flash[:notice]).to eq("The record was updated successfully")
end
it "fails to create a record and returns to the edit page" do
expect(put :update, { id: record.id, record: { title: "" } }).to render_template(:edit)
end
end
end
Now with the current user being saved Rspec throws me errors in create and update:
1) RecordsController#create creates a new record with the given title and description
Failure/Error: post :create, record: { title: title, description: description, user_id: user }
NoMethodError:
undefined method `authenticate' for nil:NilClass
# ./app/controllers/records_controller.rb:42:in `record_params'
# ./app/controllers/records_controller.rb:9:in `create'
# ./spec/controllers/records_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
# ./spec/controllers/records_controller_spec.rb:35:in `block (3 levels) in <top (required)>'
# -e:1:in `<main>'
2) RecordsController#create fails to create a record and returns to the index page
Failure/Error: expect(post :create, record: { description: description }).to render_template(:index)
NoMethodError:
undefined method `authenticate' for nil:NilClass
# ./app/controllers/records_controller.rb:42:in `record_params'
# ./app/controllers/records_controller.rb:9:in `create'
# ./spec/controllers/records_controller_spec.rb:46:in `block (3 levels) in <top (required)>'
# -e:1:in `<main>'
3) RecordsController#update find the records and sets the new given values
Failure/Error: put :update, { id: record.id, record: { title: title, description: description } }
NoMethodError:
undefined method `authenticate' for nil:NilClass
# ./app/controllers/records_controller.rb:42:in `record_params'
# ./app/controllers/records_controller.rb:20:in `update'
# ./spec/controllers/records_controller_spec.rb:62:in `block (3 levels) in <top (required)>'
# -e:1:in `<main>'
4) RecordsController#update fails to create a record and returns to the edit page
Failure/Error: expect(put :update, { id: record.id, record: { title: "" } }).to render_template(:edit)
NoMethodError:
undefined method `authenticate' for nil:NilClass
# ./app/controllers/records_controller.rb:42:in `record_params'
# ./app/controllers/records_controller.rb:20:in `update'
# ./spec/controllers/records_controller_spec.rb:72:in `block (3 levels) in <top (required)>'
You need to login the user first before you can use the current_user.
Something like:
before do
sign_in :user, create(:user)
end
Also checkout How To: Test controllers with Rails 3 and 4 (and RSpec)

RSpec fails upon a namespaced model assigned to blog

I was doing the RSpec testing with Rails and it's failing due to the namespaced model that I assigned to its associations of the model.
CLI:
Failures:
1) Blog::BlobsController GET index assigns all blog/blobs as #blobs
Failure/Error: expect(assigns(:blog::blobs)).to eq([blog::blobs])
NoMethodError:
undefined method `blobs' for :blog:Symbol
# ./spec/controllers/blog/blobs_controller_spec.rb:8:in `block (3 levels) in <top (required)>'
spec/controllers/blog/blobs_controller_spec.rb
RSpec.describe Blog::BlobsController, type: :controller do
describe "GET index" do
it "assigns all blog/blobs as #blobs" do
blobs = Blog::Blob.create!
get :index, {}
expect(assigns(:blog::blobs)).to eq([blog::blobs])
end
end
end
routes.rb
# SNIPPED FOR BREVITY...
namespace :blog do
resources :blobs
end
app/controllers/blog/blobs_controller.rb
class Blog::BlobsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show]
def index
#blobs = Blog::Blob.all
end
def new
#blob = Blog::Blob.new
end
def edit
#blob = Blog::Blob.find(params[:id])
end
def create
#blob = Blog::Blob.new(blob_params)
if #blob.save
redirect_to #blob
else
render 'new'
end
end
def update
#blob = Blog::Blob.find(params[:id])
if #blob.update(blob_params)
redirect_to #blob
else
render 'edit'
end
end
def show
#blob = Blog::Blob.find(params[:id])
end
def destroy
#blob = Blog::Blob.find(params[:id])
#blob.destroy!
redirect_to blog_blobs_path
end
private
def blob_params
params.require(:blob).permit(:title, :body)
end
end
Is there a better way to test this controller with RSpec?
UPDATE:
Failures:
1) Blog::BlobsController GET #index assigns all widgets as #widgets
Failure/Error: expect(assigns(:blobs)).to eq([blob])
TypeError:
no implicit conversion of Symbol into Integer
# /usr/local/rvm/gems/ruby-2.1.5#rails4/gems/mongo-2.0.4/lib/mongo/server_selector.rb:56:in `[]'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/gems/mongo-2.0.4/lib/mongo/server_selector.rb:56:in `get'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/gems/mongo-2.0.4/lib/mongo/client.rb:170:in `read_preference'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/gems/mongo-2.0.4/lib/mongo/collection/view/readable.rb:318:in `default_read'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/gems/mongo-2.0.4/lib/mongo/collection/view/readable.rb:251:in `read'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/gems/mongo-2.0.4/lib/mongo/collection/view/iterable.rb:38:in `each'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/bundler/gems/mongoid-26f67146a7b7/lib/mongoid/query_cache.rb:207:in `each'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/bundler/gems/mongoid-26f67146a7b7/lib/mongoid/contextual/mongo.rb:116:in `each'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/bundler/gems/mongoid-26f67146a7b7/lib/mongoid/contextual.rb:20:in `each'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/bundler/gems/mongoid-26f67146a7b7/lib/mongoid/criteria.rb:48:in `entries'
# /usr/local/rvm/gems/ruby-2.1.5#rails4/bundler/gems/mongoid-26f67146a7b7/lib/mongoid/criteria.rb:48:in `=='
# ./spec/controllers/blog/blobs_controller_spec.rb:8:in `block (3 levels) in <top (required)>'
Updated spec code:
require 'rails_helper'
RSpec.describe Blog::BlobsController, type: :controller do
describe "GET #index" do
it "assigns all blobs as #blobs" do
blob = Blog::Blob.create!
get :index, {}
expect(assigns(:blobs)).to eq([blob])
end
end
end
Your test says:
blobs = Blog::Blob.create!
This is confusing, because blobs is plural, but you're only creating one blob. So start by renaming that to blob. Then expect(assigns(:blog::blobs)).to eq([blog::blobs]) should be expect(assigns(:blobs)).to eq([blob]).
In the index action, you set #blobs = Blog::Blob.all. The assigns correspond to the controller's instance variables. There's no namespacing.

Pre-processing within a Rails Controller create method does not work

I am following Michael Hartl's tutorial, and trying to implement the reply twitter-like functionality, ie. "#122-john-smith: hello there" should be a reply to user 122.
I first tried filtering the "#XXX-AAA-AAA" part using a before_filter, but I decided to try it first in the very same Micropost#create action. So far I've got this MicropostController:
class MicropostsController < ApplicationController
before_filter :signed_in_user, only: [:create, :destroy]
before_filter :correct_user, only: [:destroy]
#before_filter :reply_to_user, only: [:create]
def index
end
def create
#micropost=current_user.microposts.build(params[:micropost])
#Rails.logger.info "hoooola"
regex=/\A#(\d)+(\w|\-|\.)+/i
message=#micropost.content.dup
isResponse=message.match(regex)[0].match(/\d+/)[0]
#micropost.response=isResponse
if #micropost.save
flash[:success]="Micropost created!"
redirect_to root_path
else
#feed_items=[]
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_path
end
private
def correct_user
#micropost = current_user.microposts.find_by_id(params[:id])
redirect_to root_path if #micropost.nil?
end
def reply_to_user
regex=/\A#(\d)+(\w|\-|\.)+/i
#I use [0] cause the output of match is a MatchData class with lots of bs
mtch=params[:micropost][:content].match(regex)[0]
#puts mtch
##micropost=current_user.microposts.build(params[:micropost])
if mtch != nil
user_id=mtch.match(/\d+/)[0]
#replied_user=User.find(user_id)
#micropost.response=user_id unless #replied_user.nil?
end
end
end
And this is the snippet test I'm trying to pass:
require 'spec_helper'
describe "MicropostPages" do
subject { page }
let(:user) { FactoryGirl.create(:user) }
before { valid_signin user }
describe "micropost creation" do
before { visit root_path }
describe "with invalid information" do
it "should not create a micropost" do
expect { click_button "Post" }.should_not change(Micropost,
:count)
end
describe "error messages" do
before { click_button "Post" }
it { should have_content('error') }
end
end
describe "with valid information" do
before { fill_in 'micropost_content', with: "Lorem ipsum" }
it "should create a micropost" do
expect { click_button "Post" }.should change(Micropost,
:count).by(1)
end
end
end
...
end
If I run these tests I get the follwing error:
Failures:
1) MicropostPages micropost creation with invalid information should not create a micropost
Failure/Error: expect { click_button "Post" }.should_not change(Micropost, :count)
NoMethodError:
undefined method `[]' for nil:NilClass
# ./app/controllers/microposts_controller.rb:14:in `create'
# (eval):2:in `click_button'
# ./spec/requests/micropost_pages_spec.rb:11:in `block (5 levels) in <top (required)>'
# ./spec/requests/micropost_pages_spec.rb:11:in `block (4 levels) in <top (required)>'
2) MicropostPages micropost creation with invalid information error messages
Failure/Error: before { click_button "Post" }
NoMethodError:
undefined method `[]' for nil:NilClass
# ./app/controllers/microposts_controller.rb:14:in `create'
# (eval):2:in `click_button'
# ./spec/requests/micropost_pages_spec.rb:14:in `block (5 levels) in <top (required)>'
However if I modify the tests and comment out all the #XXX filtering in the Micropost#create action:
def create
#micropost=current_user.microposts.build(params[:micropost])
#Rails.logger.info "hoooola"
#regex=/\A#(\d)+(\w|\-|\.)+/i
#message=#micropost.content.dup
#isResponse=message.match(regex)[0].match(/\d+/)[0]
##micropost.response=isResponse
if #micropost.save
flash[:success]="Micropost created!"
redirect_to root_path
else
#feed_items=[]
render 'static_pages/home'
end
end
The tests pass just fine and the new Micropost is not a Nil object.
It can't seem to find an explanation here.
The error comes from this line:
isResponse=message.match(regex)[0].match(/\d+/)[0]
Check if your two match calls actually match correctly. If the pattern is not found in your string, nil is returned and the [0] call is made on nil. There's two instances in this line alone where this could happen.
Try to spread it out over several lines and check the return values of your matches or extend your Regex to properly check the pattern in one go.

Resources