I'm having trouble getting this pass, although things work in both the console and the browser. Thanks for your help!
Here's my spec:
before(:each) do
#attr = {name: "Example Class"}
#create = post :create, {user_id: #user.id, student_group: #attr}
end
it "should create a user" do
lambda do
#create
end.should change {#user.student_groups.count}.by(1)
end
And my new and create actions in the controller:
def new
#student_group = #user.student_groups.new
#title = "Create a new class"
end
...
def create
#student_group = #user.student_groups.create(params[:student_group])
if #student_group.save
# for now redirect to
redirect_to classes_path, flash: { success: "#{#student_group.name} created! Next, add some students" }
# redirect_to new_student_group_student_path
else
#title = "Create a new class"
flash.now[:error] = "Something's gone wrong. Please try again!"
render 'new'
end
end
and the error:
1) StudentGroupsController POST 'create' success should create a user
Failure/Error: lambda do
result should have been changed by 1, but was changed by 0
# ./spec/controllers/student_groups_controller_spec.rb:113:in `block (4 levels) in <top (required)>'
Your lambda does not contain executable code, I think the moving creation code to this lambda will resolve the issue.
it "should create a user" do
lambda do
post :create, {user_id: #user.id, student_group: #attr}
end.should change {#user.student_groups.count}.by(1)
end
Related
I have a rails 3.2.16 app that has a model and controller to upload a csv file that contains a list of customer details. In the app itself this works fine, however I can't get the test to work.
I basically get an error that says
undefined method 'first_name,last_name,address_1,address_2,city .... etc.'
So it is trying to use the first line of the csv file as a method ... ?
The files I am using are shown below
spec (the commented out lines show things that I have tried along the way having seen other issues in SO)
it "upload a file with correct properties" do
#include Rack::Test::Methods
# #file = fixture_file_upload(Rails.root.join('spec/fixtures/files/cust-imp-good.csv'), 'text/csv')
#file = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/cust-imp-good.csv'), 'text/csv')
post :create, :customer_import => #file
response.should be_success
end
uploader model
class CustomerImport #< ActiveRecord::Base
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :file
def initialize(attributes = {})
debugger
attributes.each { |name, value| send("#{name}=", value) }
end
def persisted?
false
end
def save
if imported_customers.map(&:valid?).all?
valid_ids = true
dive_shop_ids = DiveShop.ids_array
discount_level_ids = DiscountLevel.ids_array
imported_customers.each_with_index do |customer, index|
if !dive_shop_ids.include?(customer.dive_shop_id)
errors.add :base, "Row #{index+2}: dive_shop_id #{customer.dive_shop_id} is not valid"
valid_ids = false
end
if !discount_level_ids.include?(customer.discount_level_id)
errors.add :base, "Row #{index+2}: discount_level_id #{customer.discount_level_id} is not valid"
valid_ids = false
end
end
if valid_ids
imported_customers.each(&:save!)
return_val = imported_customers.count
else
false
end
else
imported_customers.each_with_index do |customer, index|
customer.errors.each do |message|
errors.add :base, "Row #{index+2}: #{message}"
end
end
false
end
end
def imported_customers
#imported_customers ||= ImportRecord.load_imported_records("Customer", file)
end
end
From the error shown below I can see that it is failing in the initializer. Although if I put a debugger in there the initializer looks to be OK.
Output from debugger inside initializer
rdb:1 attributes
Rack::Test::UploadedFile:0x0000000b089a98 #content_type="text/csv", #original_filename="cust-imp-good.csv", #tempfile=#<File:/tmp/cust-imp-good.csv20131212-26548-ynutnh>>
rdb:1
Output from rspec failure message
Failures:
1) CustomerImportsController POST 'create' upload a file with correct properties
Failure/Error: post :create, :customer_import => #file
NoMethodError:
undefined method `first_name,last_name,address1,address2,address3,city,state,country,postcode,telephone,email,dob,local_contact,emergency_name,emergency_number,dive_shop_id,discount_level_id
=' for #<CustomerImport:0x0000000a5f7580>
# ./app/models/customer_import.rb:10:in `block in initialize'
# ./app/models/customer_import.rb:10:in `initialize'
# ./app/controllers/customer_imports_controller.rb:14:in `new'
# ./app/controllers/customer_imports_controller.rb:14:in `create'
# ./spec/controllers/customer_imports_controller_spec.rb:20:in `block (3 levels) in <top (required)>'
any help would be much appreciated I tried the solution shown in Undefined Method 'NameOfField' for #<Model:0x000...> i.e rake: db:test:prepare and bundle exec rspec . but this didn't work either
EDIT to include controller code
class CustomerImportsController < ApplicationController
before_filter do
#menu_group = "diveshop"
end
def new
#customer_import = CustomerImport.new
end
def create
if params[:customer_import] != nil
#customer_import = CustomerImport.new(params[:customer_import])
return_value = #customer_import.save # need to add #customer_import.file here
if return_value != false
addauditlog("A bulk import of customers was carried out")
redirect_to customers_url, notice: "Imported #{return_value} customers successfully."
else
render :new
end
else
flash[:error] = "You have not selected a file"
redirect_to new_customer_import_url
end
end
end
In creating the new model instance, your controller seems to have passed a hash as a parameter with a key whose value is the first line of the csv file. You'll need to share the controller code and the first line of the file you've updated in order to be able to confirm that and provide more information.
I have a bit of a confusing rSpec issue - depending how I write my code, either the tests that describe the 'failing' specs fail or the tests that describe the 'successful' specs fail.
Here are the tests for the create action:
describe "POST 'create'" do
describe "failure" do
before(:each) do
#attr = {name: "", type_of_group: ""}
#student_attr = [{name: "Joe", gender: "Male"}, {name: "sally twotrees", gender: "Female"}]
#create = post :create, student_group: #attr, student: #student_attr
end
it "should have the right title" do
#create
response.should have_selector('title', :content => "Create a new group" )
end
it "should render the 'new' page" do
#create
response.should render_template('new')
end
it "should not create a user" do
lambda do
post :create, student_group: #attr
end.should_not change {#user.student_groups.count}
end
it "should flash an error message" do
#create
flash[:error].should =~ /please/i
end
end
describe "success" do
before(:each) do
#attr = FactoryGirl.attributes_for(:student_group)
# #student_attr = {name: "test", gender: "Male"}
end
it "should create a student_group" do
lambda do
post :create, student_group: #attr
end.should change {#user.student_groups.count}.by(1)
end
it "should create students" # do
# lambda do
# post :create, student_group: #attr, student: #student_attr
# end.should change {#student_groups.students.count}.by(1)
# end
it "should flash a success message" do
post :create, student_group: #attr
flash[:success].should =~ /has been added/i
end
it "should redirect" do
post :create, student_group_id: #group, student_group: #attr
response.should be_redirect
end
end
end
All of the 'failure' tests fail with this error:
Failure/Error: #create = post :create, student_group: #attr, student: #student_attr
ActionView::Template::Error:
`#student_group[students_attributes]' is not allowed as an instance variable name
if I write the code in my controller this way:
def create
#params = params[:student_group][:students_attributes]
#student_group = #user.student_groups.build(params[:student_group])
if #student_group.save
### RE: 'defensive coding' https://stackoverflow.com/questions/14502508/undefined-method-for-nilnilclass-when-pushing-values-to-an-array
if #params.present?
### https://stackoverflow.com/questions/11355820/rails-3-2-iterate-through-an-array
#params.each do |student|
#student_group.students.create(name:"#{student[:name]}", gender: "#{student[:gender]}")
end
end
# new subject path
redirect_to class_path(#student_group), flash: { success: "#{#student_group.name} has been added successfully" }
else
#title = "Create a new group"
flash.now[:error] = "Something's gone wrong. Please try again!"
render 'new'
end
end
and all of the 'success' tests fail if the controller code is written like this:
def create
#params = params[:student_group][:students_attributes]
#student_group = #user.student_groups.build(params[:student_group])
### http://railsforum.com/viewtopic.php?pid=40056#p40056
if #params.present?
#student = Student.new
else
#student = #student_group.students.build(#params)
end
if #student_group.save
### RE: 'defensive coding' https://stackoverflow.com/questions/14502508/undefined-method-for-nilnilclass-when-pushing-values-to-an-array
if #params.present?
### https://stackoverflow.com/questions/11355820/rails-3-2-iterate-through-an-array
#params.each do |student|
#student_group.students.create(name:"#{student[:name]}", gender: "#{student[:gender]}")
end
end
# new subject path
redirect_to class_path(#student_group), flash: { success: "#{#student_group.name} has been added successfully" }
else
#title = "Create a new group"
flash.now[:error] = "Something's gone wrong. Please try again!"
render 'new'
end
end
the form code is here: https://stackoverflow.com/a/17591802/2128691
from the above code it seems that your controller code is really messed up. In case of nested attributes, u just have to save the parent object. the child objects get saved automatically if they are valid. Also u dont need to assign the params the some instance object. they should be used directly. a simple example of nested attributes can be
User
has_many :comments
accepts_nested_attributes_for :comments
Comment
belongs_to :user
ur controller code should be as
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = 'success'
redirect_to some_path and return
end
render 'new'
end
the rspec controller test case can be as
it "should create a user with comments if valid data is provided" do
post :create, "user"=>{"name"=>"Prasad", "comments_attributes"=>{"0"=>{"comment"=>"first comment"}, "1"=>{"comment"=>"second comment"}}, "commit"=>"Save"
user = assigns[:user] #assigns lets u access the instance variable from the controller in the spec
user.should be_valid
user.comments.count.should == 2 #check that all the child models are saved
user.name.should == "Prasad"
user.comments.first.comment.should == 'first comment'
user.comments.last.comment.should == 'second comment'
response.should be_redirect(some_path) #since u redirected in the code
end
seriously, u need to go through rails guides.
I ended up using this code:
def create
#student_group = #user.student_groups.new(params[:student_group])
#params = params[:student_group][:students_attributes]
#student_group = #user.student_groups.build(params[:student_group])
if #student_group.save
### RE: 'defensive coding' http://stackoverflow.com/questions/14502508/undefined-method-for-nilnilclass-when-pushing-values-to-an-array
if #params.present?
### http://stackoverflow.com/questions/11355820/rails-3-2-iterate-through-an-array
#params.each do |student|
#student_group.students.create(name:"#{student[:name]}", gender: "#{student[:gender]}")
end
end
redirect_to new_student_group_subject_path(#student_group), flash: { success: "#{#student_group.name} has been added successfully. Next, add the subjects for this group" }
else
### http://railsforum.com/viewtopic.php?pid=40056#p40056
#student = #student_group.students.build
#title = "Create a new group"
flash.now[:error] = "Something's gone wrong. Please try again!"
render 'new'
end
end
I have the following rspec test:
def valid_attributes
{ "product_id" => "1" }
end
describe "POST create" do
describe "with valid params" do
it "creates a new LineItem" do
expect {
post :create, {:line_item => valid_attributes}, valid_session #my valid_session is blank
}.to change(LineItem, :count).by(1)
end
Which fails with this error:
1) LineItemsController POST create with valid params redirects to the created line_item
Failure/Error: post :create, {:line_item => valid_attributes}, valid_session
ActiveRecord::RecordNotFound:
Couldn't find Product without an ID
# ./app/controllers/line_items_controller.rb:44:in `create'
# ./spec/controllers/line_items_controller_spec.rb:87:in `block (4 levels) in <top (required)>'
This is my controller's create action:
def create
#cart = current_cart
product = Product.find(params[:product_id])
#line_item = #cart.line_items.build(:product => product)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart, notice: 'Line item was successfully created.' }
format.json { render json: #line_item.cart, status: :created, location: #line_item }
else
format.html { render action: "new" }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
As you can see, my action expects a product_id from the request's params object. How should I work this product_id into my rspec test?
I've tried placing this before statement:
before(:each) do
ApplicationController.any_instance.stub(:product).and_return(#product = mock('product'))
end
. . . but it changes nothing. I am missing some rspec concept here somewhere.
Try like this:
describe "POST create" do
describe "with valid params" do
it "creates a new LineItem" do
expect {
post :create, :product_id => 1
}.to change(LineItem, :count).by(1)
end
Hope it helps.
I ended up resolving my issue by using a fixture instead of attempting to mock the solution as suggested in another answer.
The reason for this is that the controller does the query to get information from the database: product = Product.find(params[:product_id]) and I found a fixture-based solution was quicker to resolve my problem than one using a mock and I could not figure out how to stub the query quickly (the fixtures also help with another test on the controller so it eventually helped anyway.
For reference:
I referenced my fixture with this line toward the top of the test: fixtures :products
I changed my test to:
describe "POST create" do
describe "with valid params" do
it "creates a new LineItem" do
expect {
post :create, :product_id => products(:one).id
}.to change(LineItem, :count).by(1)
end
And here is my fixture file, products.yml:
one:
name: FirstProduct
price: 1.23
two:
name: SecondProduct
price: 4.56
I'm working on a training app which is an Ogame-Like game (https://github.com/arnlen/ogame-like).
I'm using rspec (with Capybara) in order to test my app.
I'm stacked for several hours because rspec is complaining for an error which *I can't reproduce * by myself with my browser.
Here is my rspec code :
describe 'Planet pages' do
let(:user){FactoryGirl.create(:user)}
before {sign_in user}
subject {page}
describe "new planet page" do
before {visit new_planet_path}
describe "with valid information" do
before do
visit new_planet_path
fill_in "Name", with: "MyPlanet"
click_button "Validate"
end
# This test doesn't pass
it {should have_selector('h1', text: "Planet")}
end
end
end
The failure :
1) Planet pages new planet page with valid information
Failure/Error: it {should have_selector('h1', text: "Planet")}
expected css "h1" with text "Planet" to return something
# ./spec/requests/planet_pages_spec.rb:34:in `block (4 levels) in <top (required)>'
Here is the involved code.
My function "sign_in" used by rspec (location : spec/support/utilities.rb)
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
My UsersController
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:index, :show, :edit, :update, :destroy]
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
redirect_to new_planet_path
else
render 'new'
end
[...]
My PlanetsController
class PlanetsController < ApplicationController
before_filter :signed_in_user
def index
#planets = current_user.planets
end
def new
#planet = Planet.new
end
def create
#planet = Planet.new(name: params[:planet][:name],
coordinates: generate_coordinates,
metal_ressource: 1000,
user_id: current_user.id)
if #planet.save
flash[:success] = "Welcome on your first planet!"
redirect_to action: 'index'
else
flash[:error] = "Error naming your planet"
render 'new'
end
end
end
And My Planet Index view
<% #planets.each do |planet| %>
<h1>Planet : <%= planet.name %></h1>
<p><%= "Coordinates : #{planet.coordinates}" %></p>
<% end %>
I tried to user the Capybara method "save_and_open_page", but rspec raised an error "undefined method"
I also tried step by step debugging by iterations on my spec file, and it revealed that the error occurs right after the "click_button 'Validate'". For an unknown reason, rspec seems not to be able to reach the planets_path ("index" action from PlanetsController).
I'm out, if anybody has an idea, I take it !
EDIT : SOLVED - Found the problem!
Using the "save_and_open_page" method from Capybara, I figured out what was going on: the planet created by rspec didn't have any coordinates, which was not allowed by the model.
How to debug with the wonderful "save_and_open_page" method
Add this to your gemfile : "gem 'launchy'"
Install it : bundle install
Put the command "save_and_open_page" wherever you want
Hope it could help. :)
Capybara also has a save_page method, which is easier to use as it does not seem to need the "launchy" gem. The pages are saved in tmp/capybara. In the rspec tests, be sure to use save_page inside before, it, or some other block. It will not work as a separate command. Example:
before { visit signup_path; save_page }
I'm trying to learn RSpec and writing test for CRUD actions. Here is my controller:
class ArticlesController < ApplicationController
respond_to :html, :json
before_filter :authenticate_user!
# GET /articles
# GET /articles.json
def index
#articles = current_user.articles.all
respond_with(#articles)
end
# GET /articles/1
# GET /articles/1.json
def show
#article = current_user.articles.find(params[:id])
respond_with #article
end
# GET /articles/new
# GET /articles/new.json
def new
#article = current_user.articles.build
respond_with #article
end
# GET /articles/1/edit
def edit
#article = get_article(params[:id])
end
# POST /articles
# POST /articles.json
def create
#article = current_user.articles.build(params[:article])
flash[:notice] = "Article was successfully created!" if #article.save
respond_with(#article, location: articles_path)
end
# PUT /articles/1
# PUT /articles/1.json
def update
#article = get_article(params[:id])
if #article.update_attributes(params[:article])
flash[:notice] = "Article was successfully updated."
end
respond_with #article
end
# DELETE /articles/1
# DELETE /articles/1.json
def destroy
#article = get_article(params[:id])
#article.destroy
respond_with #article
end
private
def get_article(article_id)
current_user.articles.find(article_id)
end
end
And my articles rspec:
describe ArticlesController do
def valid_attributes
{
:title => "Introducting Node.js",
:content => "Node.js is an event-driven...."
}
end
let(:article) do
build(:article, valid_attributes)
end
describe "PUT 'update'" do
before(:each) do
controller.stub_chain(:current_user, :articles, :build) { article }
end
context "success" do
before(:each) do
article.should_receive(:update_attributes).and_return(true)
put :update, id: article.id
end
it "sets notice" do
flash[:notice].should eq("Article was successfully updated!")
end
end
end
describe "POST 'create'" do
before(:each) do
controller.stub_chain(:current_user, :articles, :build) { article }
end
context "success" do
before(:each) do
article.should_receive(:save).and_return(true)
post :create
end
it "sets notice" do
flash[:notice].should eq("Article was successfully created!")
end
it "should redirect to article path" do
response.should redirect_to(articles_path)
end
end
context "failure" do
before(:each) do
article.should_receive(:save).and_return(false).as_null_object
post :create
end
it "assigns #article" do
assigns(:article).should == article
end
end
end
end
My question is when I run rspec on PUT UPDATE test is failed. But POST test is passed. I don't have any idea what is going on. I'm using Rails 3.1.1 with omniauth. I'm not using Devise. Here is the test result. Why? Please help me guys?
Failures:
1) ArticlesController PUT 'update' success sets notice
Failure/Error: put :update, id: article.id
NoMethodError:
undefined method `find' for #<Object:0xa3cfd20>
# ./app/controllers/articles_controller.rb:61:in `get_article'
# ./app/controllers/articles_controller.rb:44:in `update'
# ./spec/controllers/articles_controller_spec.rb:46:in `block (4 levels) in <top (required)>'
Finished in 24.09 seconds
5 examples, 1 failure
Here's the thing.
When you're stubbing, you're just saying "if this method chain is called, return this." There are two issues with that. 1) the code doesn't ever call build, and 2) there's no actual associations.
I believe you'd need to stub current_user.articles to return an article collection. The problem is that AR associations aren't actual arrays, they're proxies.
See this SO post and this SO post for more details. A regular array won't treat the find method like the AR method it really is, and you're not returning a single article.
Since you have the article ID, you could just return that particular article, but your goal is to return that article from within the user's articles to avoid updating someone else's (I assume).
This SO post may also help, and this.
In other words, you may want a real user there, with real associated objects, so things like find will work w/o hackery.
(I fully recognize this isn't a real answer; I've never done this via stubbing, I've used factories/etc.)