I'm trying to wrap my head about Rspec and Controller tests, more specifically a JSON request. In this case, I'm trying to hit the v1_devices_path route:
routes.rb
v1_device_scans POST /v1/devices/:device_serial_number/scans(.:format) v1/scans#create
v1_device GET /v1/devices/:serial_number(.:format) v1/devices#show
PATCH /v1/devices/:serial_number(.:format) v1/devices#update
PUT /v1/devices/:serial_number(.:format) v1/devices#update
And my controller:
controllers/v1/devices_controller.rb
class V1::DevicesController < ApplicationController
before_action :set_device, only: [:show, :update]
respond_to :json
def show
#device = Device.find_by(serial_number: params[:serial_number])
render :json => #device.as_json
end
def update
if #device.update(device_params)
render json: #device, status: :ok, location: #device
else
render json: #device.errors, status: :unprocessable_entity
end
end
private
def set_device
#device = Device.find_by!(serial_number: params[:serial_number])
end
def device_params
params.require(:device).permit(
:serial_number,
:name,
:diagnostic_checkin_status,
:diagnostic_dns,
:diagnostic_ping,
:assigned_internal_ip,
:assigned_external_ip,
:assigned_gateway_ip,
:version,
:timezone,
:task_id,
:scan_status,
:scan_progress
)
end
end
As of right now my test is super simple... just want to make sure I get some results back:
spec/controllers/v1/devices_controller_spec.rb
require 'rails_helper'
RSpec.describe V1::DevicesController, type: :controller do
it "shows device info" do
device = FactoryGirl.create(:device)
get v1_device_path(device.serial_number), :format => :json
expect(response.status).to be(200)
end
end
After some tweaking, I've gotten to the point where it looks like my url is being created correctly, but I'm still getting the no route matches error:
1) V1::DevicesController shows device info
Failure/Error: get v1_device_path(device.serial_number), :format => :json
ActionController::UrlGenerationError:
No route matches {:action=>"/v1/devices/41442305171c430ab253ba1ad95c5c61", :controller=>"v1/devices", :format=>:json}
# /usr/local/bundle/gems/rails-controller-testing-1.0.2/lib/rails/controller/testing/template_assertions.rb:61:in `process'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:33:in `block in process'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:100:in `catch'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:100:in `_catch_warden'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:33:in `process'
# /usr/local/bundle/gems/rails-controller-testing-1.0.2/lib/rails/controller/testing/integration.rb:12:in `block (2 levels) in <module:Integration>'
# ./spec/controllers/v1/devices_controller_spec.rb:11:in `block (2 levels) in <top (required)>'
What am I doing wrong?
This is another solution to not change type into request. This error shows because of adding serial_number parameter in show devise route, you can add serial_number in your get url.
RSpec.describe V1::DevicesController, type: :controller do
it "shows device info" do
device = FactoryGirl.create(:device)
get :show, params: { serial_number: device.serial_number }, :format => :json
expect(response.status).to be(200)
end
end
I hope this help you.
Alright, starting with #sebastián's comment, I changed the spec to:
require 'rails_helper'
RSpec.describe V1::DevicesController, type: :request do
it "shows device info" do
headers = {
"ACCEPT" => "application/json", # This is what Rails 4 accepts
}
device = FactoryGirl.create(:device)
get v1_device_path(device.serial_number), :headers => headers
expect(response.content_type).to eq("application/json")
end
end
And my test passes now.
Related
I'm trying to make a test to submit JSON data to my API endpoint, however it's not working. After trying various suggestions from blogs and the rspec documentation I'm still failing.
specs/controller/v1/devices_controller_spec.rb
require 'rails_helper'
RSpec.describe V1::DevicesController, type: :controller do
it "updates device info" do
#data = {}
#device = FactoryGirl.create(:device)
#data[:diagnostic_dns] = false
#data[:diagnostic_ping] = false
put :update, #data.to_json
#data.reload
response.should be_successful
end
end
I've also tried this in my test:
it "updates device info" do
device = FactoryGirl.create(:device)
device.diagnostic_dns = false
device.diagnostic_ping = false
put :update, :device, :format => :json
response.should be_successful
end
Which results in this rspec failure:
Failures:
1) V1::DevicesController updates device info
Failure/Error: put :update, #data.to_json
ArgumentError:
wrong number of arguments (given 2, expected 1)
app/controllers/v1/devices_controller.rb
class V1::DevicesController < ApplicationController
skip_before_action :authenticate_user!, only: [:show, :update], :if => Proc.new { |c| c.request.format == 'application/json' }
before_action :set_device, only: [:show, :update]
respond_to :json
def update
if #device.update(device_params)
render json: #device, status: :ok
else
render json: #device.errors, status: :unprocessable_entity
end
end
private
def set_device
#device = Device.find_by!(serial_number: params[:serial_number])
end
def device_params
params.require(:device).permit(
:serial_number,
:name,
:diagnostic_checkin_status,
:diagnostic_dns,
:diagnostic_ping,
)
end
end
And my routes.rb file
v1_device GET /v1/devices/:serial_number(.:format) v1/devices#show
PATCH /v1/devices/:serial_number(.:format) v1/devices#update
PUT /v1/devices/:serial_number(.:format) v1/devices#update
UPDATE
After changing my test submit to:
patch :update, params: {serial_number: device.serial_number, device: #data }, :format => :json
I'm now getting:
Failures:
1) V1::DevicesController updates device info
Failure/Error: response.should be_successful
expected `#<ActionDispatch::TestResponse:0x005592458af9d0 #mon_owner=nil, #mon_count=0, #mon_mutex=#<Thread::Mu...:Headers:0x00559245826d10 #req=#<ActionController::TestRequest:0x005592458afcc8 ...>>, #variant=[]>>.successful?` to return true, got false
UPDATE
I added byebug to the controller and this is what #device.errors has it in:
#messages={:diagnostic_ping=>["can't be blank"], :diagnostic_dns=>["can't be blank"]}, #details={:diagnostic_ping=>[{:error=>:blank}], :diagnostic_dns=>[{:error=>:blank}]}>
So, the variables are not being set?
To adding parameter, you can use this.
specs/controller/v1/devices_controller_spec.rb
require 'rails_helper'
RSpec.describe V1::DevicesController, type: :controller do
let(:new_attributes) {
FactoryGirl.attributes_for :device, diagnostic_dns: false, diagnostic_ping: false
}
it "updates device info" do
#device = FactoryGirl.create :device
put :update, params: {serial_number: #device.serial_number, device: new_attributes }, :format => :json
response.should be_successful
end
end
I hope this help you.
I wouldn't stress too much about getting RSpec to submit an actual JSON string to your code. Rails is in charge of turning that JSON into a params hash for you, so Rails should have the tests verifying they handle JSON properly. I would just do a normal test passing params with a format: :json verifying that you return the JSON you're expecting.
I believe that would look like
put :update, serial_number: #device.serial_number, device: #data, format: :json
I am getting the following error when I run rspec. Could really do with some help here! I am not sure if the nested resources or ajax calls are contributing to the rspec failure.
1) GoalsController GET #new renders the :new template
Failure/Error: expect(response).to render_template :new
expecting <"new"> but rendering with <[]>
# ./spec/controllers/goals_controller_spec.rb:7:in `block (3 levels) in <top (required)>'
Here are my codes as shown below.
routes.rb
Rails.application.routes.draw do
resources :strategies, :only => :none do
resources :goals
end
resources :goals, :only => :none do
resources :objectives
end
end
goals_controller.rb
class GoalsController < ApplicationController
respond_to :html, :js
def new
#strategy = Strategy.find(params[:strategy_id])
end
def index
#strategy = Strategy.find(params[:strategy_id])
end
def create
#user = User.find(current_user.id)
#strategy = Strategy.find(params[:strategy_id])
#goal = #strategy.goals.create(goal_params.merge(
start_date: #strategy.start_date,
end_date: #strategy.end_date,
created_by: #user.id))
respond_to do |format|
format.js { }
end
end
private
def goal_params
params.require(:goal).permit(:name, :budget)
end
end
goals_controller_spec.rb
require 'rails_helper'
RSpec.describe GoalsController, type: :controller do
describe 'GET #new' do
it "renders the :new template" do
get :new, strategy_id: 2
expect(response).to render_template :new
end
end
end
Like Sergey Sokolov suggested in the comment, try removing all :only => :none
in the routes.
resources will generate routes for all CRUD actions,:only => :none is basically saying you don't want it to generate at all.
I'm now making Rspec test for users_controller.rb. However I'm in trouble the error NoMethodError: undefined method 'user_url' as follow.
FF
Failures:
1) UsersController PUT update user update does not succeed
Failure/Error: put :update, {:id => user.to_param}, valid_session, :user_route => user
NoMethodError:
undefined method `user_url' for #<UsersController:0x52e40e0>
# ./app/controllers/users_controller.rb:21:in `block (2 levels) in update'
# ./app/controllers/users_controller.rb:18:in `update'
# ./spec/controllers/users_controller_spec.rb:64:in `block (3 levels) in <top (required)>'
2) UsersController PUT update user update succeeds
Failure/Error: put :update, {:id => user.to_param}, valid_session, :user_route => user
NoMethodError:
undefined method `user_url' for #<UsersController:0x53bc560>
# ./app/controllers/users_controller.rb:21:in `block (2 levels) in update'
# ./app/controllers/users_controller.rb:18:in `update'
# ./spec/controllers/users_controller_spec.rb:58:in `block (3 levels) in <top (required)>'
Finished in 0.679 seconds
2 examples, 2 failures
Failed examples:
rspec ./spec/controllers/users_controller_spec.rb:61 # UsersController PUT update user update does not succeed
rspec ./spec/controllers/users_controller_spec.rb:56 # UsersController PUT update user update succeeds
Randomized with seed 33412
users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "user#edit" }
format.json { render json: #idea.errors, status: :unprocessable_entity }
end
end
end
end
Also here is my Rspec users_controller_spec.rb. I made two tests about "POST update". One is for being updated successfully. Another is for not being updated. (About the latter, I put the stub User.stub(:update_attribute).and_return(false) which I expect that "update_attribute" returns "false" so that process proceeds to "else".)
require 'spec_helper'
describe UsersController do
let(:valid_attributes) { {
"email" => "hoge#hogehoge.com",
"password" => "12345678"
} }
def valid_session
{}
end
describe "PUT update" do
it "user update succeeds" do
user = User.create! valid_attributes
put :update, {:id => user.to_param}, valid_session
assigns(:user).should eq(user)
end
it "user update does not succeed" do
user = User.create! valid_attributes
User.stub(:update_attribute).and_return(false)
put :update, {:id => user.to_param}, valid_session
assigns(:user).should eq(user)
response.should render_template("edit")
end
end
end
I have no idea to solve this, because I cannot understand where user_url did come. So I would like to have your help.
When you use redirect_to #user, rails sends that request to UsersController#show, but it does so by calling user_url(#user). If I had to guess, you probably don't have the line that defines user_url:
resources :users
in your routes.rb file. This would automatically create the named route user_url that your controller is referencing with redirect_to #user
Alternatively, you could define the route yourself in your routes.rb file like so:
get "/users/show" => "users#show", as: :user
But that's not really the 'Rails-y' way to do it. At any time, you can run the command rake routes in the terminal to see all the named routes you have defined in your routes.rb file. If user isn't there, then you need to define it like I mentioned above.
More info on named routes here: http://guides.rubyonrails.org/routing.html#singular-resources
If you are using devise then check if the following method returns anything.
def after_sign_in_path_for(resource)
in application_controller.rb
If the method returns nothing you will receive the error:
undefined method `user_url' for #
I also ended up removing
stored_location_for(resource)
in after_sign_in_path_for(resource) because it was causing an endless loop. Refer to this answer for details.
rails:3 Devise signup Filter chain halted as :require_no_authentication rendered or redirected
I'm new to rails and I built an app without doing TDD but am now going back and trying to pass all the tests. I've passed most of them but there are a few left relating to the same issue that I can't figure out. The app functions correctly as well, I just can't pass these tests.
The tests fail and provide this:
1) ProductsController POST create with valid params assigns a newly created product as #product
Failure/Error: post :create, {:product => valid_attributes}, valid_session
Paperclip::AdapterRegistry::NoHandlerError:
No handler found for "#<File:0x007fc6d17b28f8>"
# ./app/controllers/products_controller.rb:43:in `new'
# ./app/controllers/products_controller.rb:43:in `create'
# ./spec/controllers/products_controller_spec.rb:86:in `block (4 levels) in <top (required)>'
2) ProductsController POST create with valid params creates a new Product
Failure/Error: post :create, {:product => valid_attributes}, valid_session
Paperclip::AdapterRegistry::NoHandlerError:
No handler found for "#<File:0x007fc6d1757cf0>"
# ./app/controllers/products_controller.rb:43:in `new'
# ./app/controllers/products_controller.rb:43:in `create'
# ./spec/controllers/products_controller_spec.rb:81:in `block (5 levels) in <top (required)>'
# ./spec/controllers/products_controller_spec.rb:80:in `block (4 levels) in <top (required)>'
3) ProductsController POST create with valid params redirects to the created product
Failure/Error: post :create, {:product => valid_attributes}, valid_session
Paperclip::AdapterRegistry::NoHandlerError:
No handler found for "#<File:0x007fc6d36b3dd8>"
# ./app/controllers/products_controller.rb:43:in `new'
# ./app/controllers/products_controller.rb:43:in `create'
# ./spec/controllers/products_controller_spec.rb:92:in `block (4 levels) in <top (required)>'
The "create" method in my controller:
def create
#product = Product.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to admin_path, notice: 'Product was successfully created.' }
format.json { render json: #product, status: :created, location: #product }
else
format.html { render action: "new" }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
My Model:
class Product < ActiveRecord::Base
attr_accessible :designed, :features, :photo, :manufactured, :name, :case_study
has_attached_file :photo, {
:styles => {
:thumb => "x50>",
:small => "x150>",
:detail => "x600>"
}
}.merge(PAPERCLIP_STORAGE_OPTIONS)
validates_attachment_presence :photo
validates_attachment_size :photo, :less_than => 5.megabytes
validates_attachment_content_type :photo, :content_type => ['image/jpeg', 'image/png']
end
My test:
before(:each) do
#image = File.new(Rails.root + 'spec/fixtures/images/test.png')
end
def valid_attributes
{ "photo" => #image }
end
describe "POST create" do
describe "with valid params" do
it "creates a new Product" do
expect {
post :create, {:product => valid_attributes}, valid_session
}.to change(Product, :count).by(1)
end
it "assigns a newly created product as #product" do
post :create, {:product => valid_attributes}, valid_session
assigns(:product).should be_a(Product)
assigns(:product).should be_persisted
end
it "redirects to the created product" do
post :create, {:product => valid_attributes}, valid_session
response.should redirect_to(admin_path)
end
end
end
If you're using Rails 3.2, try sending an UploadedFile instead of File in your tests. UploadedFile takes a filename, and a content-type in its initializer.
before(:each) do
#image = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/images/test.png'), 'image/png')
end
You might have to include Rack::Test::Methods in your test or test helper.
You can also use fixture_file_upload as a shortcut to Rack::Test::UploadedFile.new like this:
post :create, product: { photo: fixture_file_upload('spec/fixtures/images/test.png', 'image/png') }
Have you added the Paperclip attachment to your databases -- eg created and run migrations? Including the test database?
Here is the error in rspec:
CategoriesController GET 'update' should be successful
Failure/Error: get 'update'
ActiveRecord::RecordNotFound:
Couldn't find Category without an ID
# c:in `find'
# ./app/controllers/categories_controller.rb:45:in `update'
# ./spec/controllers/categories_controller_spec.rb:35:in `block (3 levels) in <top (required)>'
Here is the code in controller:
def edit
#category = Category.find(params[:id])
end
def update
#category = Category.find(params[:id])
##category.reload. caused nil.reload error
if #category.update_attributes(params[:category], :as => :roles_update)
#category = Category.find(params[:id])
redirect_to #category, :notice => 'Category was successfully updated'
else
#categories = Category.all
render 'index'
end
end
Here is the rspec code:
describe "GET 'update'" do
it "should be successful" do
get 'update'
response.should be_success
end
end
Any thoughts? Thanks.
You pasted the create action instead of the update action. Also, you are trying to test the update action with a get request.. it should be with a put request if you are following the conventions.
If you had, say, the update action implemented... you would test more or less like:
describe CategoriesController do
let(:category) { mock_model(Category).as_null_object }
describe "PUT update" do
before do
Category.should_receive(:find).with(5).and_return(category)
end
context "when a category updates succesfully" do
before do
category.stub(:update_attributes).and_return(true)
end
it "redirects to the categories page" do
put :update, :id => 5, :category => { :some_val => 4 }
response.should redirect_to(categories_path)
end
it "sets the flash message" do
put :update, :id => 5, :category => { :some_val => 4 }
flash[:notice].should eq("Category was succesfully updated")
end
end
context "when a category does not update successfully" do
before do
category.stub(:update_attributes).and_return(false)
end
it "sets the flash message"
it "redirects to the categories page"
# etc etc
end
end
end
To get to this point (meaning the addition of mock models, stubs, what have you) you would normally start "fresh" so to speak and work your way up TDD style. Hope it helps