Hello Programmers & Developers!!!, I'm a beginner in RoR and creating a simple project in rails to learn its working, so in that project I'm facing a problem in writing a spec for the create method of controller. When I'm trying to pass the associate attributes of the object in spec file, in controller it isn't get all the attributes.
In the create method of subjects_controller.rb file.I've created a variable called attr in this variable I'm storing all the values sent from subjects_controller_spec.rb file.
attr=(params.require(:subject).permit(:name)).merge(:classroom_ids=>params[:subject][:classroom_ids],:school_ids=>params[:subject][:school_ids])
Now, If I print the value of the attr using p attr in console it's output is the exact output that I want, which is
{"name"=>"Computer", "classroom_ids"=>["1", "2"], "school_ids"=>["1"]}
But, now I'm doing #subject = Subject.new(attr) and printing value of #subject gives the following output
#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>
and after running the test I'm getting my test failed and then I printed the error p #subject.errors it gave me the below output
#<ActiveModel::Errors:0x007fc35444a218 #base=#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>, #messages={:school_ids=>["is not a number"], :classroom_ids=>["is not a number"]}>
So, here is my actual question is why #subject in subjects_controller.rb is not having values of classroom_ids and school_ids? If any solution or suggestion is there then please help me to sort out this problem.
Below I'm providing you all the necessary details to understand the actual problem.
Ruby Version 2.2.4
Rails Version 4.2.0
Database MySQL
Model file subject.rb
class Subject < ActiveRecord::Base
has_and_belongs_to_many :schools
has_and_belongs_to_many :teachers
has_and_belongs_to_many :classrooms
has_and_belongs_to_many :students
validates_presence_of :name, :school_ids, :classroom_ids
validates_numericality_of :school_ids, :classroom_ids
end
Controller file subjects_controller_spec.rb
require 'rails_helper'
RSpec.describe SubjectsController, type: :controller do
before(:each) do
#school1 = FactoryGirl.create(:school)
#classroom1 = FactoryGirl.create(:classroom, :school_id=>#school1.id)
#classroom2 = FactoryGirl.create(:classroom, :school_id=>#school1.id)
#subject = FactoryGirl.build(:subject)
#subject.classrooms<<#classroom1
#subject.classrooms<<#classroom2
#subject.schools<<#school1
end
context "POST create" do
it "should be success" do
# p #subject
# p #subject.classrooms
# p #subject.classroom_ids
attributes=#subject.attributes.merge(:classroom_ids=>#subject.classroom_ids,:school_ids=>#subject.school_ids)
# In below line, I'm sending all the values to the controller to create a new subject.
post :create, :subject=>attributes
response.status.should eq 201
end
end
end
Controller file subjects_controller.rb
class SubjectsController < ApplicationController
before_action :set_subject, only: [:show, :edit, :update, :destroy]
# GET /subjects
def index
#subjects = Subject.all
end
# GET /subjects/1
def show
end
# GET /subjects/new
def new
#subject = Subject.new
end
# GET /subjects/1/edit
def edit
end
# POST /subjects
def create
attr=(params.require(:subject).permit(:name)).merge(:classroom_ids=>params[:subject][:classroom_ids],:school_ids=>params[:subject][:school_ids])
p attr ### here it prints all the values which I want to create subject.###
#subject = Subject.new(attr)
p #subject ### here is the actual problem, It's not printing all the values that need to create a new subject.###
if #subject.save
redirect_to #subject, notice: 'Subject was successfully created.', status: :created
else
p #subject.errors
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /subjects/1
def update
if #subject.update(subject_params)
redirect_to #subject, notice: 'Subject was successfully updated.', status: :ok
else
render :edit, :status => :unprocessable_entity
end
end
# DELETE /subjects/1
def destroy
#subject.destroy
redirect_to subjects_url, notice: 'Subject was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_subject
#subject = Subject.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def subject_params
params.require(:subject).permit(:name, :school_ids, :classroom_ids)
end
end
Factory file subjects.rb
FactoryGirl.define do
factory :subject do
name "Computer"
end
end
RSpec Test Report
rspec spec/controllers/subjects_controller_spec.rb
{"name"=>"Computer", "classroom_ids"=>["1", "2"], "school_ids"=>["1"]}
#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>
#<ActiveModel::Errors:0x007fcdfe8f1a28 #base=#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>, #messages={:school_ids=>["is not a number"], :classroom_ids=>["is not a number"]}>
F
Failures:
1) SubjectsController POST create should be success
Failure/Error: response.status.should eq 201
expected: 201
got: 422
(compared using ==)
# ./spec/controllers/subjects_controller_spec.rb:21:in `block (3 levels) in <top (required)>'
Deprecation Warnings:
Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }` instead. Called from /Users/vishal/project/school_system/spec/controllers/subjects_controller_spec.rb:21:in `block (3 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations to
identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the
deprecation warnings into errors, giving you the full backtrace.
1 deprecation warning total
Finished in 0.40113 seconds (files took 3.03 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/subjects_controller_spec.rb:14 # SubjectsController POST create should be success
Coverage report generated for RSpec to /Users/vishal/project/school_system/coverage. 49 / 332 LOC (14.76%) covered.
For more details you can refer this Github link.
Thanks For Help In Advance.
[ "1", "2" ] is not array of integer but String! #subject has classroom_ids and school_ids, but params always treat input values as String, so validation error occurs in your Subject model. So try below to transform String to Integer:
params[:subject][:classroom_ids].map(&:to_i)
params[:subject][:school_ids].map(&:to_i)
How about this?
restore below without map method in the controller:
params[:subject][:classroom_ids]
params[:subject][:school_ids]
In my PC, by modifing the Subject model as below from your github link and passed the test.
Could you try this?
Class Subject < ActiveRecord::Base
has_and_belongs_to_many :schools
has_and_belongs_to_many :teachers
has_and_belongs_to_many :classrooms
has_and_belongs_to_many :students
validates_presence_of :name, :school_ids, :classroom_ids
validate :validate_classroom_ids
validate :validate_school_ids
private
def validate_classroom_ids
if classroom_ids.any?{ |id| !id.is_a?(Integer) }
errors.add(:classroom_ids, 'is not a number')
return false
end
end
def validate_school_ids
if school_ids.any?{ |id| !id.is_a?(Integer) }
errors.add(:school_ids, 'is not a number')
return false
end
end
end
Related
Alright, I keep trying to make this test work and for some reason it does not want to work. I don't know if I missed something or what but I can't figure out what the heck is going on with this code. Every thing seems to point to it being correct but I don't know. Anyway here is what I have done, which I think is correct but obviously it isn't since it keeps failing.
These are my test attributes, the invalid ones are failing for some reason.
let(:valid_attributes){
#user = User.create!(:email => "email#gmail.com", :password => 'password')
{:name => "name", :user_id => #user.id}
}
let(:invalid_attributes){
#user = User.create!(:email => "email#gmail.com", :password => 'password')
{:name => "", :user_id => #user.id}
}
Here's my post request:
describe "POST #create" do
context "with valid attributes" do
it "describes a survey created with valid attributes" do
expect{
post :create, survey: valid_attributes
}.to change(Survey, :count).by(1)
end
it "redirects the user to the survey's detail page" do
post :create, {survey: valid_attributes}
expect(response).to redirect_to(Survey.last)
end
end
context "with invalid attributes" do
it "describes a survey created with invalid attributes" do
post :create, {survey: invalid_attributes}
expect(assigns(:survey)).to be_a_new(Survey)
end
it "re-renders the new template" do
post :create, {survey: invalid_attributes}
expect(response).to render_template('new')
end
end
end
And of course my controller method, which is implemented, as such this shouldn't be failing, especially because it is doing exactly what that stuff indicates.
def create
#survey = Survey.new(survey_params)
respond_to do |format|
if #survey.save
format.html { redirect_to #survey, notice: 'Survey was successfully created.' }
else
format.html { render :new }
end
end
end
I'm using strong parameters as well, don't know if that makes a difference, I don't think it should but anyway this is what they have:
def set_survey
#survey = Survey.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def survey_params
params.require(:survey).permit(:name, :user_id)
end
Finally, this is what my error messages say, which make no sense to me, especially the first one since the object seems to meet all the standards for it to be a survey.
Error 1:
1) SurveysController POST #create with invalid attributes describes a survey created with invalid attributes
Failure/Error: expect(assigns(:survey)).to be_a_new(Survey)
expected #<Survey id: 187, name: "", user_id: 257, created_at: "2015-10-11 04:46:35", updated_at: "2015-10-11 04:46:35"> to be a new Survey(id: integer, name: string, user_id: integer, created_at: datetime, updated_at: datetime)
# ./spec/controllers/surveys_controller_spec.rb:82:in `block (4 levels) in <top (required)>'
Error 2:
2) SurveysController POST #create with invalid attributes re-renders the new template
Failure/Error: expect(response).to render_template('new')
expecting <"new"> but rendering with <[]>
# ./spec/controllers/surveys_controller_spec.rb:87:in `block (4 levels) in <top (required)>'
What is happening is when you send a request using invalid_attributes, your controller still takes the successful path through your code. You can tell from your two failures. Rendering with <[]> happens on a redirect, and object instances only have an id and created_at if they've been saved.
This would indicate that you're not validating presence of name in your Survey model:
# app/models/survey.rb
class Survey < ActiveRecord::Base
belongs_to :user
validates :name, presence: true
end
and thus it is saved successfully with a blank name.
I previously fixed an issue with some code that works though it is a little ugly. Problem now is that it breaks my tests! The idea here is that I can create a Campaign and associate 1 zip-file and one-to-many pdfs.
Previous question and solution:
Rails 4.2: Unknown Attribute or Server Error in Log
Here is the failure message:
console
1) CampaignsController POST #create with valid params
Failure/Error: post :create, campaign: attributes_for(:campaign)
ActiveRecord::RecordNotFound:
Couldn't find Uploadzip with 'id'=
# ./app/controllers/campaigns_controller.rb:15:in `create'
# ./spec/controllers/campaigns_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
..and the rest of the code.
spec/factories/campaigns.rb
FactoryGirl.define do
factory :campaign do |x|
x.sequence(:name) { |y| "Rockfest 201#{y} Orange County" }
x.sequence(:comment) { |y| "Total attendance is #{y}" }
end
end
spec/controllers/campaigns_controller.rb
describe "POST #create" do
context "with valid params" do
before(:each) do
post :create, campaign: attributes_for(:campaign)
end
.........
end
app/controllers/campaigns_controller.rb
class CampaignsController < ApplicationController
......................
def create
#campaign = Campaign.new(campaign_params)
if #campaign.save
zip = Uploadzip.find(params[:uploadzip_id])
zip.campaign = #campaign
zip.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
................
end
end
.......................
private
def campaign_params
params.require(:campaign).permit(:name, :comment, :campaign_id, uploadpdf_ids: [])
end
end
This appears simple and I assume it is, yet I've tried quit a few things and can't seem to get it to pass. How would I support the new controller logic in this test? Any help is appreciated.
UPDATE
With zetitic's advice, I created the following code in which successfully passes.
before(:each) do
#uploadzip = create(:uploadzip)
post :create, campaign: attributes_for(:campaign), uploadzip_id: #uploadzip
end
Add the uploadedzip_id to the posted params:
before(:each) do
post :create, campaign: attributes_for(:campaign), uploadedzip_id: 123456
end
I am trying to test to see if posting to a create method in my controller triggers a callback I defined with after_save
Here's the controller method being posted to
def create
#guest = Guest.new(guest_params)
#hotel = Hotel.find(visit_params[:hotel_id])
#set visit local times to UTC
#visit= Visit.new(visit_params)
#visit.checked_out_at = (DateTime.now.utc + visit_params[:checked_out_at].to_i.to_i.days).change(hour: #visit.hotel.checkout_time.hour)
#visit.checked_in_at = Time.now.utc
##visit.user_id = current_user.id
#self_serve = (params[:self_serve] && params[:self_serve] == "true")
if #guest.save
#visit.guest_id = #guest.id
if #visit.save
if #self_serve
flash[:notice] = "#{#visit.guest.name}, you have successfully checked in!."
redirect_to guest_checkin_hotel_path(#visit.hotel)
else
flash[:notice] = "You have successfully checked in #{#visit.guest.name}."
redirect_to hotel_path(#visit.hotel)
end
else
render "new"
end
else
render "new"
end
end
Here's my spec/controllers/guests_controller_spec.rb test that is failing
RSpec.describe GuestsController, :type => :controller do
describe "#create" do
let!(:params) do { name: "John Smith", mobile_number: "9095551234" } end
context "when new guest is saved" do
it "triggers create_check_in_messages callback" do
post :create, params
expect(response).to receive(:create_check_in_messages)
end
end
end
end
Here is my models/concerns/visit_message.rb callback file
module VisitMessage
extend ActiveSupport::Concern
included do
after_save :create_check_in_messages
end
def create_check_in_messages
. . .
end
end
Here is the fail message when I run 'rspec spec/controllers/guests_controller_spec.rb'
1) GuestsController#create when new guest is saved triggers create_check_in_messages callback
Failure/Error: post :create, params
ActionController::ParameterMissing:
param is missing or the value is empty: guest
# ./app/controllers/guests_controller.rb:63:in `guest_params'
# ./app/controllers/guests_controller.rb:10:in `create'
# ./spec/controllers/guests_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
I've been searching all over stackoverflow with no luck. I appreciate any help!
I am assuming that the guest_params method in the controller looks something like this:
def guest_params
params.require(:guest).permit(....)
end
If that is the case, you need to update the POST call in your test case thusly:
post :create, {guest: params}
On a side note, your controller is unnecessarily bloated. I would read up on working with associated models to streamline your code, specifically, using accepts_nested_attributes_for:
http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
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.
I am attempting to create an API with Rails using BDD with RSpec.
Rails version is 3.1.1, Ruby version is 1.9.2, Devise version is 1.5.3, and rspec version is 2.7.0. I am relatively new to Rails and very new to RSpec.
I have defined a simple RSpec as follows to test a FormsController with essentially no logic.
describe FormsController, " handling GET /forms" do
include Devise::TestHelpers
render_views
before do
user = Factory.create(:user) # Handle Devise authentication
user.confirm!
sign_in user
#form = mock_model(Form)
Form.stub!(:all).and_return([ #form ])
end
it "gets successfully" do
get :index, :format => :json
response.should be_success
end
it "finds all forms" do
Form.should_receive(:all).and_return([#form])
get :index, :format => :json
Rails.logger.info "*** response.body="+response.body
end
end
Form controller code is very simple currently.
class FormsController < ApplicationController
before_filter :authenticate_user!
# GET /forms
# GET /forms.json
def index
#forms = Form.find_all_by_owner_id(current_user.id)
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #forms }
end
end
end
When I run the spec, "finds all forms" always fails with
Failure/Error: Form.should_receive(:all).and_return([#form])
(<Form(id: integer, title: string, owner_id: integer, created_at: datetime, updated_at: datetime) (class)>).all(any args)
expected: 1 time
received: 0 times
The output from log/test.log shows:
*** response.body=[]
Why? I feel that the problem stems from Form.stub!(:all).and_return([ #form ]), but I am not sure how to debug.
Thanks in advance.
It would help to post your controller code (that is being tested). The error says that the declaration Form.should_receive(:all).and_return([#form]) has not been satisfied. The declaration says you should have code like this in your controller's action: Form.all.
find_all_by_owner_id is not the same as Form.all. find_all_by_owner_id ends up doing
Form.where(...).all
which doesn't match the expectations you've set. In your particular case I'd tell should_receive that I'm expecting a call to find_all_by_owner_id rather than all.
After much more trial and error, the following solution worked for me.
I migrated from mocking the Form model to using Factory Girl to create the full model
I then updated the test to use to_json to compare the response against the model.
The spec is as follows.
describe FormsController, " handling GET /forms" do
include Devise::TestHelpers
render_views
before do
user = Factory.create(:user) # Handle Devise authentication
user.confirm!
sign_in user
#form1 = Factory.create(:form)
end
it "gets successfully" do
get :index, :format => :json
response.should be_success
end
it "finds all forms" do
get :index, :format => :json
response.body.should == [ #form1 ].to_json
Rails.logger.info "*** response.body="+response.body
end
end