Rails API post in request spec should pass params but getting empty - ruby-on-rails-5

To start, in my request spec sites.spec.rb I have this test:
describe "POST /v1/sites" do
let(:valid_attributes) { { url: "www.example.com", site_code: "123456" } }
context 'when the request is valid' do
before { post v1_sites_path, params: valid_attributes }
it 'creates a site' do
expect(json['url']).to eq("www.example.com")
expect(json['site_code']).to eq("123456")
end
it 'returns status code 201' do
expect(response).to have_http_status(201)
end
end
I then get a failing test for "creates a site"...
1) Sites POST /v1/sites when the request is valid creates a site
Failure/Error: expect(json['url']).to eq("www.example.com")
expected: "www.example.com"
got: ["can't be blank"]
(compared using ==)
# ./spec/requests/sites_spec.rb:61:in `block (4 levels) in <top (required)>'
# ./spec/rails_helper.rb:84:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:83:in `block (2 levels) in <top (required)>'
Now this technically makes sense because my Site model has validates :url, :site_code, presence: true. So the test is failing because the post is not passing the params correctly.
Lastly, here is the controller:
module Api::V1
class SitesController < BaseApiController
before_action :set_site, only: [:show, :update, :destroy]
# GET /sites
def index
#sites = Site.all
render json: #sites
end
# GET /sites/1
def show
render json: #site
end
# POST /sites
def create
#site = Site.new(site_params)
if #site.save
render json: #site, status: :created, location: #site
else
render json: #site.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /sites/1
def update
if #site.update(site_params)
render json: #site
else
render json: #site.errors, status: :unprocessable_entity
end
end
# DELETE /sites/1
def destroy
#site.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_site
#site = Site.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def site_params
# params.require(:data).require(:attributes).permit(:url, :side_code, :user_id)
# params.require(:site).permit(:url, :side_code, :user_id)
params.fetch(:site, {}).permit(:url, :side_code)
end
end
end
I am speculating that the way I am passing parameters to the post for Rails API is perhaps not formatted or correctly or something else entirely. I did play with the params in the test block trying data: { attributes: valid_attributes } with no luck.
Any thoughts or suggestions are greatly appreciated!

This problem was indeed due to format of the parameters I was passing to POST request in the test block. I tested the POST via the command line and watched the rails server to see how the params were coming through. They looked like this:
Parameters: {"site_code"=>"123456", "url"=>"www.updated.com", "subdomain"=>"api", "id"=>"2", "site"=>{"site_code"=>"123456", "url"=>"www.updated.com"}}
Then in my sites_spec.rb, I copied this format for the valid params of the post request:
let(:valid_attributes) { { "site"=>{"url"=>"www.example.com", "user_id"=>user_id, "site_code"=>"123456"} } }
This works. The JSON format of the params needed to be formatted in the test block the same way they would be if it was real JSON request.

Related

RSpec file upload raises 'undefined method' for header row

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.

result should have been changed by 1, but wasn't?

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

Testing Multiple Custom Validators with RSpec

I am trying to run specs for two custom validators:
spec/validators/email_validator_spec.rb
spec/validators/phone_validator_spec.rb
When I run bundle exec rspec spec/validators/ the phone_validator_spec.rb spec fails:
1) PhoneValidator with a valid phone number should be valid
Failure/Error: subject.should be_valid
expected valid? to return true, got false
# ./spec/validators/phone_validator_spec.rb:20:in `block (4 levels) in <top (required)>'
# ./spec/validators/phone_validator_spec.rb:18:in `each'
# ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'
However, when I run that spec individually using the command bundle exec rspec spec/validators/phone_validator_spec.rb, it passes.
When I remove the email_validator_spec.rb then phone_validator_spec.rb passes using the command bundle exec rspec spec/validators/.
I expect both specs to pass when I run bundle exec rspec spec/validators/. Can anyone explain to me what is happening?
Update:
Used zetetic's tip to print out the error hash:
1) PhoneValidator with a valid phone number should be valid
Failure/Error: subject.errors.should == {}
expected: {}
got: #<ActiveModel::Errors:0x37b2460 #base=#<Validatable:0x37b2700 #validation_context=nil, #errors=#<ActiveModel::Errors:0x37b2460 ...>, #phone_number="1112223333">, #messages={:email=>["is invalid"]}> (using ==)
Diff:
## -1 +1,8 ##
+#<ActiveModel::Errors:0x37b2460
+ #base=
+ #<Validatable:0x37b2700
+ #errors=#<ActiveModel::Errors:0x37b2460 ...>,
+ #phone_number="1112223333",
+ #validation_context=nil>,
+ #messages={:email=>["is invalid"]}>
# ./spec/validators/phone_validator_spec.rb:21:in `block (4 levels) in <top (required)>'
# ./spec/validators/phone_validator_spec.rb:18:in `each'
# ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'
It appears the Validatable class definitions are combined when both specs are run. Is this behavior expected? If I use distinct class names, both specs pass.
spec/validators/phone_validator_spec.rb
require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/phone_validator')
class Validatable
include ActiveModel::Validations
attr_accessor :phone_number
validates :phone_number, phone: true
end
describe PhoneValidator do
subject { Validatable.new }
describe "with a valid phone number" do
it "should be valid" do
phone_numbers = ["1112223333", "123222ABCD"]
phone_numbers.each do |phone_number|
subject.phone_number = phone_number
subject.should be_valid
end
end
end
end
app/validators/phone_validator.rb
class PhoneValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
return if value.blank?
unless value =~ /^[A-Za-z0-9]{10}$/
object.errors[attribute] << (options[:message] || "is not formatted properly")
end
end
end
spec/validators/email_validator_spec.rb
require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/email_validator')
class Validatable
include ActiveModel::Validations
attr_accessor :email
validates :email, email: true
end
describe EmailValidator do
subject { Validatable.new }
describe "with a valid email address" do
it "should be valid" do
addresses = %w[user#foo.COM A_US-ER#f.b.org frst.lst#foo.jp a+b#baz.cn]
addresses.each do |valid_address|
subject.email = valid_address
subject.should be_valid
end
end
end
describe "with an invalid phone number" do
it "should be invalid" do
addresses = %w[user#foo,com user_at_foo.org example.user#foo]
addresses.each do |invalid_address|
subject.email = invalid_address
subject.should be_invalid
end
end
end
end
app/validators/email_validator.rb
require 'mail'
class EmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
begin
m = Mail::Address.new(value)
# We must check that value contains a domain and that value is an email address
r = m.domain && m.address == value
t = m.__send__(:tree)
# We need to dig into treetop
# A valid domain must have dot_atom_text elements size > 1
# user#localhost is excluded
# treetop must respond to domain
# We exclude valid email values like <user#localhost.com>
# Hence we use m.__send__(tree).domain
r &&= (t.domain.dot_atom_text.elements.size > 1)
rescue => e
r = false
end
object.errors[attribute] << (options[:message] || "is invalid") unless r
end
end
Using rails 3.2.11, rspec-rails 2.11.0
Your model instance is invalid but you don't know why. Try changing
subject.should be_valid
to
subject.valid?
subject.errors.should == {}
Now the failure message will print out the error hash.
Another tip: Don't rescue Exception.
EDIT
It appears the Validatable class definitions are combined when both specs are run. Is this behavior expected?
Yes, that is normal for Ruby classes. When both spec files are required, each Validatable class body is executed, so you end up with a class that contains both validations.
You need to isolate the validations either by making the subjects pass the validation that is not under test, eg:
subject { Validatable.new(:email => "some value") }
or testing for the specific error message from the validation under test:
subject.valid?
subject.errors(:email).should include("is invalid")
PS. Seriously -- don't rescue Exception. Nothing good will come of that.
I run in to this problem myself, and yes you could rename the class but the solution I used is to create and teardown the Validatable class inside your spec.
Here's a code snippet:
describe "HttpUriValidator",
"Custom validator to ensure URL is a valid URI." do
# Create the dummy class once when the test is run.
before(:all) do
class Validatable
include ActiveModel::Validations
attr_accessor :url
validates :url, http_uri: true
end
end
# Must tearing down the class or it will taint other tests using its
# name.
after(:all) { Object.send(:remove_const, :Validatable) }
subject { Validatable.new }
EDIT::
Just a heads up when you are declaring a Module wrapping your tested class (to avoid namespacing other classes in the test) ie.
module Foo::Bar
describe Something do
after(:all) { Foo::Bar.send(:remove_const, :Testable) }
end
end
you will have to remove the constant from that namespace rather then Object.

RSpec let method gives nil when used in specs

When I run this test
require 'spec_helper'
describe AssignmentsController do
let(:user) { create(:user) }
let(:course) { create(:course) }
describe "GET 'index'" do
it "returns http success" do
assignment = user.assignments.build(name: "Hello 2", start_date: "5/20/2000", due_date: "5/21/2000")
get :index
assigns(:assignment).should eq([assignment])
end
end
end
I get this
Failure:
1) AssignmentsController GET 'index' returns http success
Failure/Error: get :index
NoMethodError:
undefined method `assignments' for nil:NilClass
# ./app/controllers/assignments_controller.rb:5:in `index'
# ./spec/controllers/assignments_controller_spec.rb:29:in `block (3 levels) in <top (required)>'
Why this be since I defined the user variable with lets above
If you look carefully, you'll see that the error originating from your controller on line 5, not the spec file. Everything should be fine with the let statement the way you have it.

Need help Rspec test

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.)