I am getting this error when using RSpec to test an index action and response in a Rails controller:
JSON::ParserError:
A JSON text must at least contain two octets!
The most common fix -- including render_views -- is not working and nil is not being passed in. The test is not hitting the view. When I insert render json: {test: 'hello world'}, status: 200 and return in the index action of the controller, and a pry in the view (index.json.jbuilder) and after the get :index in the test, I can see there is a response body. If I amend the test expectation to expect(response).to render_template '[]' I can see the empty array that should be in the response body. Why is render_views failing and how to I get it working again?
Here is the index_spec.rb:
require 'rails_helper'
RSpec.describe ThingsController, type: :controller do
render_views
let(:json_response) { JSON.parse(response.body) }
let(:status) { response.status }
let(:user) { create(:user_with_associations) }
subject{ ThingsController }
describe "GET #index" do
context "(success cases)" do
before(:each) do
expect_any_instance_of(subject).to receive(:set_user_by_token).and_return(user)
end
context "and when there are no things" do
before(:each) do
get :index
end
it "returns a 200 status" do
expect(status).to eq 200
end
it "returns a top level key of data with an empty array" do
expect(json_response["data"]).to eq []
end
end
end
end
Here is the rails_helper.rb:
ENV["RAILS_ENV"] ||= 'test'
require_relative 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
config.infer_spec_type_from_file_location!
end
And here is the spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require 'factory_girl_rails'
require 'faker'
include ActionDispatch::TestProcess
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.include FactoryGirl::Syntax::Methods
config.before do
FactoryGirl.factories.clear
FactoryGirl.find_definitions
end
end
And here's the controller action under test things_controller.rb:
class ThingsController < ApplicationController
before_action :authenticate_user!, only: [ :index ]
before_action -> {check_last_updated("Thing")}, only: [ :index ]
def index
#things = #current_user.things.in_last_three_months.approved_and_unapproved.order(start_time: :asc)
end
end
Rails 4.2.0
Ruby 2.1.2
RSpec 3.5.4
This is my first question here, so let me know if there is other information that should be included.
You're not correctly stubbing out the authentication in your spec. You say
expect_any_instance_of(subject).to receive(:set_user_by_token).and_return(user)
but you should say
allow_any_instance_of(subject).to receive(:set_user_by_token).and_return(user)
As a general rule, *_any_instance_of methods should be avoided if possible because they can be ambiguous in more nuanced situations. In controller specs, you can use controller to access the instance of the controller being tested. e.g.
allow(controller).to receive(:set_user_by_token).and_return(user)
Related
I'm having an issue logging in a user with Devise/Rspec to hit a route for testing. I'm used the support/controller_macros module as outlined by devise, but whenever I try using
login_user in any part of my test I get the error: before is not available from within an example (e.g. an it block) or from constructs that run in the scope of an example (e.g. before, let, etc). It is only available on an example group (e.g. a describe or context block).
I've tried a moving things around, making sure all of my requires are set up correctly, etc.
My test:
require "rails_helper"
RSpec.describe BallotsController, type: :controller do
describe "index" do
it "renders" do
login_user
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
end
(I've tried adding login_user inside the describe block, and the upper block as well)
My controller_macros:
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user_confirmed]
user = FactoryBot.create(:user_confirmed)
sign_in user
end
end
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
user = FactoryBot.create(:admin)
sign_in user
end
end
end
My spec helper:
require "rails_helper"
require_relative "support/controller_macros"
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.include ControllerMacros, :type => :controller
config.include Devise::Test::ControllerHelpers, :type => :controller
config.example_status_persistence_file_path = "spec/examples.txt"
config.disable_monkey_patching!
if config.files_to_run.one?
config.default_formatter = "doc"
end
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
end
I expect it to log a user in, and for the controller to correctly hit the index route. Thank you!
Only issue in the code(to resolve the error, without digging into the debate of if this method should be used or not) is that you have login user within the it block, which can't be because it is calling before(:each).
If you see the device documentation, you will also see that it does not have this call in it block, but rather outside of it block in a describe block. Which applies this call to all it blocks in that describe block.
Your code willcbecome:
RSpec.describe BallotsController, type: :controller do
describe "index" do
login_user
it "renders" do
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
end
The way I prefer:
In your controller_macros, replace the login_user with:
def login_user(user)
#request.env["devise.mapping"] = Devise.mappings[:user_confirmed]
sign_in user
end
Now, wherever you want to login user, you can do it something like:
RSpec.describe BallotsController, type: :controller do
describe "index" do
let(:user) { FactoryBot.create(:user) }
it "renders" do
login_user(user)
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
# OR
describe "index" do
let(:user) { FactoryBot.create(:user) }
before(:each) do
login_user(user)
end
it "renders" do
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
end
posts_controller.rb
class PostsController < ApplicationController
respond_to :html, :xml, :json
before_filter :authenticate_user!, :except => [:show, :index]
before_filter :admin_only, :except => [:show, :index]
def new
#post = Post.new
end
end
spec_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'forgery'
require 'populators'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.mock_with :rspec
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/test/fixtures"
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.after(:suite) do
DatabaseCleaner.clean
end
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = false
end
posts_controller_spec.rb
require File.dirname(__FILE__) + '/../spec_helper'
describe PostsController do
fixtures :all
include Devise::TestHelpers
render_views
before(:each) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.start
end
after(:each) do
DatabaseCleaner.clean
end
it "new action should render new template" do
get :new
response.should render_template(:new)
end
end
ruby 1.8.7, rspec 2.11, Rails 3.2.19
I get this error expecting <"new"> but rendering with <""> how can I pass this case ... I tried many suggestions but didnt get any success .. Please Help Am stuck with it ... :(
NOTE: I cant change the code of a controller
# Use the sign_in helper to sign in a fixture `User` record.
user = users(:one)
#request.env['warden'].stub(:authenticate!).and_return(user)
#controller.stub(:current_user).and_return(user)
get :new
assert_template 'new'
this works for me using https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs and thanx #Зелёный for the direction :)
saviors.
I'm having a trouble with cleaning database after each RSpec example.
The thing is, that when I run rspec command, users_controller_spec.rb complains that there are more records than the example expects. Indeed the records are being created as it says if I check with rails c.
when I run the this suite alone, it will be successful, so I assume it is because DatabaseCleaner doesn't clean the user records which other specs create(the number of user records matches the extra records users_controller_spec example claims to be). They are created in before :all block(if that matters).
Here is my rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
require 'devise'
require 'admin/v1/dashboard_controller'
# Requires supporting ruby files with custom matchers and macros, etc, in
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.include Devise::Test::ControllerHelpers, type: :controller
config.include ControllerMacros, type: :controller
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
config.include FactoryGirl::Syntax::Methods
config.infer_spec_type_from_file_location!
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
end
users_controller.rb
describe 'GET #index' do
it 'populates an array of users' do
user1 = create(:user)
user2 = create(:user)
get :index
expect(assigns(:users)).to match_array([user1, user2])
end
it 'renders :index template' do
get :index, {}
expect(response).to render_template :index
end
end
UPDATE1: this is where the extra user records are created
require 'rails_helper'
describe Admin::V1::MessagesController do
let(:admin_user) do
admin_user = double('admin_user')
allow(request.env['warden']).to receive(:authenticate!).and_return(admin_user)
allow(controller).to receive(:current_admin_v1_admin_user).and_return(admin_user)
p '==='
end
before { login_admin_user admin_user }
describe 'GET #index' do
it 'renders :index template' do
get :index, {}
expect(response).to render_template :index
end
end
describe 'GET #get_users' do
before :all do
#user1 = create(:user, nickname: 'hiro')
#user2 = create(:user, nickname: 'elise')
end
context 'with params' do
it 'populates an array of users matching on nickname' do
get :get_users, format: :json, query: 'h'
expect(assigns(:users)).to match_array([#user1])
end
end
context 'without params' do
it 'populates an array of all users' do
get :get_users, format: :json
expect(assigns(:users)).to match_array([#user1, #user2])
end
end
end
describe 'GET #get_messages' do
before :all do
#user1 = create(:user)
#user2 = create(:user)
#message1 = create(:message, user_id: #user1.id)
#message2 = create(:message, user_id: #user1.id)
#message3 = create(:message, user_id: #user2.id)
end
context 'with user_id' do
it 'populates an array of messages with the user_id' do
get :get_messages, format: :json, user_id: #user1.id
expect(assigns(:messages)).to match_array([#message1, #message2])
end
end
end
end
Unfortunately RSpec's before(:all) does not play nicely with transactional tests. The code in before(:all) gets run before the transaction is opened, meaning any records created there will not be rolled back when the transaction is aborted. You are responsible for manually cleaning these items up in an after(:all).
See rspec-rails#496 and Using before(:all) in RSpec will cause you lots of trouble unless you know what you are doing
after(:all) do
# before/after(:all) is not transactional; see https://www.relishapp.com/rspec/rspec-rails/docs/transactions
DatabaseCleaner.clean_with(:truncation)
end
I have this route:
post 'create', to: 'applications#create'
this controller:
class ApplicationsController < ApplicationController
def create
end
end
and this test:
require "spec_helper"
describe ApplicationsController do
describe "routing" do
it 'routes to #create' do
post('/applications').should route_to('applications#create')
end
end
end
and this is my spec_helper:
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = :should
end
config.mock_with :rspec do |c|
c.syntax = :should
end
config.use_transactional_fixtures = true
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
end
Why when I run
rspec ./spec/routing
I get this error?
Failure/Error: post('/applications').should route_to('applications#create')
NoMethodError:
undefined method `post' for RSpec
I tried changing the test using expect but with no luck. neither with a different class name.
What should I do to test my routes?
UPDATE
If I do this:
it "should return status 200" do
post('/applications'), {}
response.status.should be(200)
end
I get the same error
I am not sure it can help you but I had the same problem when passing to Rspec 3
According to https://relishapp.com/rspec/rspec-rails/docs/directory-structure
we should enable that option to pass automatically metadata.
# spec/rails_helper.rb
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
Once this was added my specs passed.
i'm using rails 4.1.1 and rspec 3.0.3.
here is the rspec code.
require 'rails_helper'
describe MembersController, type: :controller do
describe 'GET index' do
it 'should be success' do
puts "11111"
get :index
puts "33333"
expect(response).to render_template :index
end
end
end
and :index action of controller is,
def index
puts "22222"
#members = Member.order('mem_id DESC').paginate(page: params[:page]||1, per_page: params[:per_page]||30)
end
==============================
and i did 'rake spec', i exepcted the console to print like this.
11111
22222
33333
but the result is,
11111
33333
and it makes an error message like this.
Failure/Error: expect(response).to render_template :index
expecting <"index"> but rendering with <[]>
what's wrong with my rspec?
----------------------------------- UPDATE QUESTION ------------------------------
this is the spec/rails_helper.rb (all comments are removed)
ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = false
config.infer_spec_type_from_file_location!
end