Basically I'm stumped as to why this particular test is failing. The page works fine when I go in and test it out by hand but the test keeps failing. First off, here's the error.
2) Setlist Pages Edit page adding a song
Failure/Error: click_button submit
ActionController::RoutingError:
No route matches {:action=>"edit", :controller=>"setlists", :id=>nil}
# ./app/controllers/allocations_controller.rb:15:in `create'
# (eval):2:in `click_button'
# ./spec/requests/setlist_pages_spec.rb:79:in `block (4 levels) in <top (required)>'
I realise that the id is set to nil but I know that when the test initially visits the page it renders because tests for content pass. I'm not sure why it points to the create controller when I'm testing the edit action as well? The test is shown below:
before do
#setlist = Setlist.create(date: Date.today, morning: true)
end
...
describe "Edit page" do
let(:admin) { FactoryGirl.create(:admin) }
let(:submit){ "Add Song" }
before do
#secondSong = FactoryGirl.create(:song)
sign_in admin
visit edit_setlist_path(#setlist)
end
it{ should have_content("Edit a Setlist")}
describe "adding a song" do
before do
select("#{#secondSong.title} by #{#secondSong.artist}", from: 'Songs')
click_button submit
end
it{ should have_selector('div.alert.alert-success')}
it "should create a new allocation" do
expect{click_button submit}.to change(Allocation, :count).by(1)
end
end # end adding a song
end # end edit test
Controller code as requested:
def create
#setlist = Setlist.new(params[:setlist])
if #setlist.save
#success!
flash[:success] = "Setlist saved"
##setlist.allocations.build produce invalid allocation with nil id
redirect_to setlist_path(#setlist)
else
#FAIL!
render 'new'
end
end
def edit
#songs= Song.search(params[:search])
##songs = Song.all(order: 'title')
#setlist = Setlist.find(params[:id])
#allocations = #setlist.allocations
#allocation = Allocation.new
#selections = Song.all.collect {|s| [ [s.title, s.artist].join(" by "), s.id ]}
end
def update
#setlist = Setlist.find(params[:id])
#selections = Song.all.collect {|s| [ [s.title, s.artist].join(" by "), s.id] }
#allocations = #setlist.allocations
#allocation = Allocation.new
#Allocation parameters
#allocation.song_id = params[:allocation][:song_id]
#allocation.setlist_id = #setlist.id
#allocation.songPosition = #setlist.songs.count + 1
if #setlist.update_attributes(params[:setlist])
if #allocation.save
flash[:success] = "SETLIST SAVED!"
redirect_to edit_setlist_path(#setlist)
else
flash[:fail] = "Sorry there was an error adding songs to the setlist"
render 'edit'
end
else
flash[:fail] = "Invalid Set"
render 'edit'
end
end
Any pointers would be much appreciated!
Related
Background: I've got an after_action callback in my controller, which takes the string address, processes it and stores longitude and latitude in corresponding fields. I want to test this.
This SO question, as well as this article only consider update methods, but at least, they are quite clear, because I've already got an object to work with.
So my question is - how to find this newly created record? This SO question led me to this code:
require 'rails_helper'
RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do
context "POST methods" do
describe "#edit and #create" do
it "encodes and stores lang/lot correctly" do
post :create, general_setting: FactoryGirl.attributes_for(:general_setting)
expect(assigns(:general_setting).long).to eq(37.568021)
# expect(general_setting.long).to eq(37.568021)
# expect(general_setting.lat).to eq(55.805553)
end
end
end
end
But using the code in the answer, I get this error:
Failure/Error: expect(assigns(:general_setting).long).to eq(37.568021)
NoMethodError:
undefined method `long' for nil:NilClass
Update #1:
This is my new controller spec code:
RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do
context 'POST methods' do
before(:each) do
allow(subject).to receive(:set_long_lat)
end
describe 'post create' do
before(:each) do
post :create, params: { general_setting: FactoryGirl.attributes_for(:general_setting) }
end
it "saves the record with valid attributes" do
expect{subject}.to change{GeneralSetting.count}.by(1)
end
it 'calls :set_long_lat' do
expect(subject).to have_received(:set_long_lat)
end
end
end
describe '#set_long_lat' do
# spec for method
end
end
Update #2:
Here is my controller code:
class Admin::Settings::GeneralSettingsController < AdminController
include CrudConcern
before_action :find_general_setting, only: [:edit, :destroy, :update, :set_long_lat]
after_action :set_long_lat
def index
#general_settings = GeneralSetting.all
end
def new
#general_setting = GeneralSetting.new
# Билдим для того, что бы было видно сразу одно поле и пользователь не должен
# кликать на "добавить телефон"
#general_setting.phones.build
#general_setting.opening_hours.build
end
def edit
# Тоже самое, что и с нью - если телефонов нет вообще, то показываем одно пустое поле
if #general_setting.phones.blank?
#general_setting.phones.build
end
if #general_setting.opening_hours.blank?
#general_setting.opening_hours.build
end
end
def create
#general_setting = GeneralSetting.new(general_setting_params)
create_helper(#general_setting, "edit_admin_settings_general_setting_path")
end
def destroy
destroy_helper(#general_setting, "admin_settings_general_settings_path")
end
def update
# debug
# #general_setting.update(language: create_hash(params[:general_setting][:language]))
#general_setting.language = create_hash(params[:general_setting][:language])
update_helper(#general_setting, "edit_admin_settings_general_setting_path", general_setting_params)
end
private
def set_long_lat
geocoder = Geocoder.new
data = geocoder.encode!(#general_setting.address)
#general_setting.update!(long: data[0], lat: data[1])
end
def find_general_setting
#general_setting = GeneralSetting.find(params[:id])
end
def general_setting_params
params.require(:general_setting).permit(GeneralSetting.attribute_names.map(&:to_sym).push(
phones_attributes: [:id, :value, :_destroy, :general_setting_id ]).push(
opening_hours_attributes: [:id, :title, :value, :_destroy, :deneral_setting_id]) )
end
def create_hash(params)
language_hash = Hash.new
params.each do |param|
language_hash[param.to_sym] = param.to_sym
end
return language_hash
end
end
(If it helps - I've got a lot of similar crud-actions, that is why I've put them all in a concern controller)
module CrudConcern
extend ActiveSupport::Concern
include Language
included do
helper_method :create_helper, :update_helper, :destroy_helper, :get_locales
end
def get_locales
#remaining_locales = Language.get_remaining_locales
end
def create_helper(object, path)
if object.save!
respond_to do |format|
format.html {
redirect_to send(path, object)
flash[:primary] = "Well done!"
}
end
else
render :new
flash[:danger] = "Something not quite right"
end
#remaining_locales = Language.get_remaining_locales
end
def update_helper(object, path, params)
if object.update!(params)
respond_to do |format|
format.html {
redirect_to send(path, object)
flash[:primary] = "Well done!"
}
end
else
render :edit
flash[:danger] = "Something's not quite right"
end
end
def destroy_helper(object, path)
if object.destroy
respond_to do |format|
format.html {
redirect_to send(path)
flash[:primary] = "Well done"
}
end
else
render :index
flash[:danger] = "Something's not quite right"
end
end
end
Update #3
It's not the ideal solution, but, somehow, controller tests just won't work. I've moved my callback into the model and updated my general_setting_spec test.
class GeneralSetting < ApplicationRecord
after_save :set_long_lat
validates :url, presence: true
private
def set_long_lat
geocoder = Geocoder.new
data = geocoder.encode(self.address)
self.update_column(:long, data[0])
self.update_column(:lat, data[1])
end
end
My tests now:
RSpec.describe GeneralSetting, type: :model do
let (:regular) { FactoryGirl.build(:general_setting) }
describe "checking other validations" do
it "is invalid with no url" do
expect{
invalid.save
}.not_to change(GeneralSetting, :count)
end
it 'autofills the longitude' do
expect{ regular.save }.to change{ regular.long }.from(nil).to(37.568021)
end
it 'autofills the latitude' do
expect{ regular.save }.to change{ regular.lat }.from(nil).to(55.805078)
end
end
end
I would test expectation that controller calls method specified in after_action and make a separate test for that method.
Something like:
context 'POST methods' do
before(:each) do
allow(subject).to receive(:method_from_callback)
end
describe 'post create' do
before(:each) do
post :create, params: { general_setting: attributes_for(:general_setting) }
end
it 'calls :method_from_callback' do
expect(subject).to have_received(:method_from_callback)
end
end
end
describe '#method_from_callback' do
# spec for method
end
Be sure to use your method name instead of :method_from_callback pay attention that I've used rspec 3.5 syntax (wrapped request request parameters into params).
I'm setting up basic tests for a rails app, I'm wondering how the reload function works. Rails generated the basic crud tests, I adapted them to my need, but it seems like the reload function doesn't work.
I create a user with valid params, then ask to change its name, but it seems like nothing's updated (output is still name: 'Toto' instead of name: 'Tata').
Here's the code sample, I just followed the basic rspec tutorial, still, nothing gets updated. Any idea why ?
context "with valid params" do
let(:new_attributes) { {name: 'Tata'} }
it "updates the requested creator" do
creator = Creator.create! valid_attributes
puts creator.id
puts new_attributes
put :update, :id => creator.id, :creator => new_attributes
creator.reload
puts creator.name.to_yaml
expect(creator.attributes).to include( { "name" => 'Tata' } )
...
Here's the matching controller method
def update
#creator = Creator.find(params[:id])
authorize #creator
#creator.completed = true
params[:hidden] == 'false' ? #creator.hidden = false : #creator.hidden = true
#creator.update(creator_params)
if params[:user].present?
if params[:user][:picture].present?
#creator.picture = params[:user][:picture]
end
end
if #creator.save
if iban.nil? || (iban && !iban.valid?)
flash[:alert] = "Veuillez entrer un IBAN valide"
redirect_to edit_creator_path(#creator)
elsif params[:brief_id].present?
flash[:notice] = "Informations mises à jour"
redirect_to brief_path(params[:brief_id])
else
flash[:notice] = "Informations mises à jour"
redirect_to creator_path(#creator)
end
else
puts #creator.errors.full_messages
render :edit
end
end
Tested in browser and works fine. Test error says "expected result to have changed from 0 to 1, but did not change". Is this a factory issue or rspec issue? Why is it not changing?
Error:
Failures:
1) ShortLinksController Short links controller Clicking a short link increments the click counter by 1
Failure/Error: expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
expected result to have changed from 0 to 1, but did not change
# ./spec/controllers/short_links_controller_spec.rb:34:in `block (4 levels) in <top (required)>'
Rspec:
it "increments the click counter by 1" do
short_link = create(:short_link)
expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
end
Controller:
def url_dispatch
id = params[:id]
record = ShortLink.where(["short_link = ?", id]).first
if record.update(click_counter: record.click_counter + 1)
redirect_to record.redirect_to
else
render '/not_found'
end
end
Factory:
FactoryGirl.define do
factory :short_link do
redirect_to "http://google.com"
title "This is the google page"
short_link "xGh7u"
click_counter 0
owner Owner.create!(first_name: "Bob", last_name: "Diller", email: "bdiller#example.com")
end
end
per Fab's request, here is how I'm currently working around the issue.
context 'save invocations' do
before(:each) do
#org = create(:organization)
user = create(:user, organization: #org, is_admin: true)
sign_in user
end
it 'valid scenario' do
user2 = create(:user, organization: #org, is_admin: false)
put :update, id: user2, user: { is_admin: true }
user2.reload
expect(response).to have_http_status(204)
expect(user2.is_admin).to eq true
end
end
Here I'm calling user2.reload in order to get the updated attributes from the user2 factory.
I don't know why the expect{} syntax doesn't work for factories but you could refactor your code like this:
it "increments the click counter by 1" do
short_link = create(:short_link)
count = short_link.click_counter
get :url_dispatch, { id: short_link.short_link }
short_link.reload
expect(short_link.click_counter).to eq count + 1
end
Again I'm not saying this is best practice, I just couldn't find anything in the FactoryGirl documentation regarding RSpec 3 expect syntax in controllers that update attributes.
i try to test method create in my controller:
def create
#fund = Fund.new({started_at: Date.strptime(params[:fund].delete(:started_at), '%m/%d/%Y')}.merge(fund_params))
if #fund.save
flash[:alert] = "Fund #{#fund.name} saved!"
redirect_to funds_path
else
flash[:error] = "Fund #{#fund.name} could not be saved"
render :edit
end
end
file spec:
it 'Can create a new fund' do
fund = FactoryGirl.create(:fund)
post :create
expect(response).to redirect_to(funds_path)
end
And show this error:
NoMethodError: undefined method `delete' for nil:NilClass
The error is in this line, for the method 'delete':
#fund = Fund.new({started_at: Date.strptime(params[:fund].delete(:started_at),...
i don't know how solve this problem, Thanks.
As the error message says, that's because params[:fund] is nil. Try to send the proper parameters to the create action:
post :create, fund: { add_here_needed_params_for_this_action }
Example:
post :create, fund: { started_at: Date.today }
I am new to ror and I'm stuck with writing tests in Capybara .
What I need to do is to test the creation of a new Case
by user clicks the button . But when I check if the glasses did not see created .
require 'spec_helper'
feature "User can create new case" to
background do
# user = FactoryGirl.create (: sysadmin_user) # user.make_current
#plan = FactoryGirl.create(:plan)
# account = FactoryGirl.create (: account, plan: #plan, user: #user)
#trial = FactoryGirl.create trial (:trial, account: #account)
# trial.make_current
# trial_phase = FactoryGirl.create (: trial_phase, trial: #trial)
login (# user.email, # user.password)
end
scenario 'create new case' to
Case.where puts ("created_by =?", # user.id).count #returns 0
fill_in 'case_subject_identifier', with 'MyNewTest'
expect {click_button ('Create New Case')}.to change(Case,:count).by(1)
Case.where puts ("created_by =?", # user.id).count #returns 0
end
end
And this is controller :
POST /cases as HTML
def create`
#case = Case.where("subject_identifier = ? && trial_id = ?", params[:case]
[:subject_identifier], #current_trial.id).first
if #case || params[:case][:subject_identifier].blank?
respond_to :js
else`
params[:case][:subject_identifier]
=sanitize_value(params[:case[:subject_identifier])
if params[:case]
#case = Case.create(params[:case])``
if current_user.trial_member?(#current_trial, current_user)
#case.update_attributes(:location_id => current_user.default_location_id)
else
#case.update_attributes(:location_id => Location.find_by_name("None").id)
end
#case.update_attributes(:trial_phase_id
=>TrialPhase.find_by_name("None").id,:access
=> "None")
#redirect_to "/trial/#{#current_trial.id.to_s}/cases/#{#case.id.to_s}/case_view"
respond_to do |format|
format.js { render :js => %(window.location.pathname='#{'/trial
/'+#current_trial.id.to_s+'/cases/'+#case.id.to_s+'/case_view'}') }
end
end
end