Test not passing: undefined method `authenticate!' for nil:NilClass? - ruby-on-rails

I have the following failure:
Failures:
1) RelationshipsController creating a relationship with Ajax should increment the Relationship count
Failure/Error: xhr :post, :create, relationship: { followed_id: other_user.id }
NoMethodError:
undefined method `authenticate!' for nil:NilClass
# ./spec/controllers/relationships_controller_spec.rb:14:in `block (4 levels) in <top (required)>'
# ./spec/controllers/relationships_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
But it is very strange, if I visit the site, the thing works (the followers counter does increment if I click the Follow button:
And the weirdest thing is that there isn't any authenticate! method in relationships_controller_spec.rb:
require 'spec_helper'
describe RelationshipsController do
let(:user) { FactoryGirl.create(:user) }
let(:other_user) { FactoryGirl.create(:user) }
before { sign_in user }
describe "creating a relationship with Ajax" do
it "should increment the Relationship count" do
expect do
xhr :post, :create, relationship: { followed_id: other_user.id }
end.to change(Relationship, :count).by(1)
end
it "should respond with success" do
xhr :post, :create, relationship: { followed_id: other_user.id }
response.should be_success
end
end
describe "destroying a relationship with Ajax" do
before { user.follow!(other_user) }
let(:relationship) { user.relationships.find_by_followed_id(other_user) }
it "should decrement the Relationship count" do
expect do
xhr :delete, :destroy, id: relationship.id
end.to change(Relationship, :count).by(-1)
end
it "should respond with success" do
xhr :delete, :destroy, id: relationship.id
response.should be_success
end
end
end
Neither in the controller:
class RelationshipsController < ApplicationController
before_filter :authenticate_user!
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
What could be the problem?
(By the way, these test were made following the Ruby on Rails Tutorial. After that, I removed all the authentication system because I wanted to use Devise.)

Setting config.include Devise::TestHelpers, :type => :controller no longer works in later versions of Devise. What you need to do is log out an anonymous user to set up the proper variables:
before :each do
sign_out :user
end

I had to add this:
spec_helpers.rb:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end

Related

rspec for the controller without factory girl

I am trying to write spec code for my controller it gets failed. And i am not sure where it gets failed.
Controller Code
def index
#users = User.all
end
def update
authorize! :update, #user
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_index_path }
else
format.html { render :index }
end
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.permit(:active)
end
Spec Code for the above controller
RSpec.describe UserController, type: :controller do
describe 'GET #index' do
let(:user) {User.create!(name: "hari")}
context 'with user details'do
it 'loads correct user details' do
get :index
expect(response).to permit(:user)
end
end
context 'without user details' do
it 'doesnot loads correct user details' do
get :index
expect(response).not_to permit(:user)
end
end
end
describe 'Patch #update' do
context 'when valid params' do
let(:attr) do
{active: 'true'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'redirects to user_index_path ' do
expect(response).redirect_to(user_index_path)
end
it 'sets active state' do
expect(#user.active?('true')).to be true
end
end
context 'when invalid param' do
let(:attr) do
{active: 'nil'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'render index' do
expect(respone.status).to eq(200)
end
it 'doesnot change active state' do
expect(#user.active?(nil)).to be true
end
end
end
end
I am just a beginner and tried the spec code for my controller by checking https://relishapp.com/rspec/rspec-rails/docs/gettingstarted. Can you help me where my spec goes wrong or could anyone give me a few test examples for these methods or could redirect me to an rspec guide? the index method is getting failed
and my
terminal log is
1) UserController GET #index with user details loads correct user details
Failure/Error: expect(response).to permit(:user)
NoMethodError:
undefined method `permit' for #<RSpec::ExampleGroups::UserController::GETIndex::WithUserDetails:0x00005614152406b0>
Did you mean? print
# ./spec/controllers/user_controller_spec.rb:10:in `block (4 levels) in <top (required)>'

Rails & RSpec: Testing CRUD actions with shared examples

Testing RESTful actions of multiple Rails controllers with RSpec can generate a lot of code repetition. The following code is my first attempt at using shared examples to DRY things up.
Here is what I don't like about the code, could not find a better way and would like your help to improve:
The shared examples require that specific variables are set within let blocks within the controller spec (high coupling). I have tried to use the model name to infer the factory name and create the test data within the share examples. It works well to create the record and records variables. However, some models require the presence of associations and FactoryGirl.attributes_for does not create associated records, so validation fails. So, valid_attributes are created differently for different models. The only (likely bad) way I could think of creating valid_attributes within shared examples is to pass a string containing the code used to create the attributes and evaluate it (eval) within the shared examples
The tests that assert redirection use eval to call Rails' route/path helpers. Different controllers in this app have different redirect behaviors. After creating or updating a record, some controllers redirect to the #show action, others to #index. The problem is that when expecting a redirect to #show, AFAIK, we have to know the record ID in order to build the expected URL. And we don't know the record ID within the controller spec. We only know it within the shared examples. So how can we pass an expected redirect URL from the controller spec to the shared example if we do not yet know what that URL is (because we don't know the record ID)?
Also, please let me know if you spot any additional issues.
The controller spec:
# spec/controllers/quotes_controller_spec.rb
require "rails_helper"
RSpec.describe QuotesController, :focus, :type => :controller do
login_admin
let(:model) { Quote }
let(:record) { FactoryGirl.create(:quote) }
let(:records) { FactoryGirl.create_pair(:quote) }
let(:valid_attributes) { FactoryGirl.attributes_for(:quote, quote: "New quote") }
let(:invalid_attributes) { valid_attributes.update(quote: nil) }
include_examples "GET #index"
include_examples "GET #show"
include_examples "GET #new"
include_examples "GET #edit"
include_examples "POST #create", "quote_path(assigns(:quote))"
include_examples "PATCH #update", "quote_url"
include_examples "DELETE #destroy", "quotes_url"
end
The shared examples:
# spec/support/shared_examples/controller_restful_actions.rb
def ivar_name(model, plural: false)
if plural
model.name.pluralize.underscore.to_sym
else
model.name.underscore.to_sym
end
end
def record_name(model)
model.name.underscore.to_sym
end
RSpec.shared_examples "GET #index" do
describe "GET #index" do
it "requires login" do
sign_out current_user
get :index
expect(response).to require_login
end
it "enforces authorization" do
get :index
expect(controller).to enforce_authorization
end
it "populates instance variable with an array of records" do
get :index
expect(assigns(ivar_name(model, plural: true))).to match_array(records)
end
end
end
RSpec.shared_examples "GET #show" do
describe "GET #show" do
it "requires login" do
sign_out current_user
get :show, id: record
expect(response).to require_login
end
it "enforces authorization" do
get :show, id: record
expect(controller).to enforce_authorization
end
it "assigns the requested record to an instance variable" do
get :show, id: record
expect(assigns(ivar_name(model))).to eq(record)
end
end
end
RSpec.shared_examples "GET #new" do
describe "GET #new" do
it "requires login" do
sign_out current_user
get :new
expect(response).to require_login
end
it "enforces authorization" do
get :new
expect(controller).to enforce_authorization
end
it "assigns a new record to an instance variable" do
get :new
expect(assigns(ivar_name(model))).to be_a_new(model)
end
end
end
RSpec.shared_examples "GET #edit" do
describe "GET #edit" do
let(:record) { FactoryGirl.create(factory_name(model)) }
it "requires login" do
sign_out current_user
get :edit, id: record
expect(response).to require_login
end
it "enforces authorization" do
get :edit, id: record
expect(controller).to enforce_authorization
end
it "assigns the requested record to an instance variable" do
get :edit, id: record
expect(assigns(ivar_name(model))).to eq(record)
end
end
end
RSpec.shared_examples "POST #create" do |redirect_path_helper|
describe "POST #create" do
it "requires login" do
sign_out current_user
post :create, { record_name(model) => valid_attributes }
expect(response).to require_login
end
it "enforces authorization" do
post :create, { record_name(model) => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "saves the new record in the database" do
expect{
post :create, { record_name(model) => valid_attributes }
}.to change(model, :count).by(1)
end
it "assigns a newly created but unsaved record to an instance variable" do
post :create, { record_name(model) => valid_attributes }
expect(assigns(ivar_name(model))).to be_a(model)
expect(assigns(ivar_name(model))).to be_persisted
end
it "redirects to #{redirect_path_helper}" do
post :create, { record_name(model) => valid_attributes }
expect(response).to redirect_to(eval(redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not save the new record in the database" do
expect{
post :create, { record_name(model) => invalid_attributes }
}.not_to change(model, :count)
end
it "assigns a newly created but unsaved record an instance variable" do
post :create, { record_name(model) => invalid_attributes }
expect(assigns(ivar_name(model))).to be_a_new(model)
end
it "re-renders the :new template" do
post :create, { record_name(model) => invalid_attributes }
expect(response).to render_template(:new)
end
end
end
end
RSpec.shared_examples "PATCH #update" do |redirect_path_helper|
describe "PATCH #update" do
let(:record) { FactoryGirl.create(factory_name(model)) }
it "requires login" do
sign_out current_user
patch :update, { :id => record, record_name(model) => valid_attributes }
expect(response).to require_login
end
it "enforces authorization" do
patch :update, { :id => record, record_name(model) => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "updates the requested record" do
patch :update, { :id => record, record_name(model) => valid_attributes }
record.reload
expect(record).to have_attributes(valid_attributes)
end
it "assigns the requested record to an instance variable" do
put :update, { :id => record, record_name(model) => valid_attributes }
expect(assigns(ivar_name(model))).to eq(record)
end
it "redirects to #{redirect_path_helper}" do
patch :update, { :id => record, record_name(model) => valid_attributes }
expect(response).to redirect_to(eval(redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not update the requested record" do
expect {
patch :update, { :id => record, record_name(model) => invalid_attributes }
}.not_to change { record.reload.attributes }
end
it "assigns the record to an instance variable" do
patch :update, { :id => record, record_name(model) => invalid_attributes }
expect(assigns(ivar_name(model))).to eq(record)
end
it "re-renders the :edit template" do
patch :update, { :id => record, record_name(model) => invalid_attributes }
expect(response).to render_template(:edit)
end
end
end
end
RSpec.shared_examples "DELETE #destroy" do |redirect_path_helper|
describe "DELETE #destroy" do
it "requires login" do
sign_out current_user
delete :destroy, id: record
expect(response).to require_login
end
it "enforces authorization" do
delete :destroy, id: record
expect(controller).to enforce_authorization
end
it "deletes the record" do
# Records are lazily created. Here we must force its creation.
record
expect{
delete :destroy, id: record
}.to change(model, :count).by(-1)
end
it "redirects to #{redirect_path_helper}" do
delete :destroy, id: record
expect(response).to redirect_to(eval(redirect_path_helper))
end
end
end
Probably not an answer but too long for a comment:
First of all you can wrap all of those in a shared_examples_for block e.g.
shared_examples_for 'a CRUD Controller' do
context "GET #index" do
it "requires login" do
sign_out current_user
get :index
expect(response).to require_login
end
####
end
context "GET #show" do
it "requires login" do
sign_out current_user
get :show, id: record
expect(response).to require_login
end
####
end
end
Secondly You can have shared examples inside shared examples to the above can be
shared_examples_for 'a CRUD Controller' do
shared_examples_for 'authenticatable' do |view:,params:{}|
it "requires login" do
sign_out current_user
get view, **params
expect(response).to require_login
end
end
context "GET #index" do
it_behaves_like 'authenticatable', view: :index
####
end
context "GET #show" do
it_behaves_like 'authenticatable', view: :show, id: record
####
end
end
Third you can assign variables inside a it_behaves_like block eg.
RSpec.describe QuotesController, :focus, :type => :controller do
login_admin
it_behaves_like 'a CRUD Controller' do
let(:model) { Quote }
let(:record) { FactoryGirl.create(:quote) }
let(:records) { FactoryGirl.create_pair(:quote) }
let(:valid_attributes) { FactoryGirl.attributes_for(:quote, quote: "New quote") }
let(:invalid_attributes) { valid_attributes.update(quote: nil) }
end
end
Fourth this too can be simplified
shared_examples_for 'a CRUD Controller' do |model:|
singular,plural = 2.times.map { |n| model.name.pluralize(n).underscore.to_sym }
let(:record) { FactoryGirl.create(singular)
let(:records) {FactoryGirl.create_pair(singular) }
let(:valid_attributes) do
# build should create the nested associations correctly as long
# as your factories are right
FactoryGirl.build(singular).attributes.delete_if do |k,_|
# this is because ActiveRecord#attributes contains columns
# you don't want to be considered updateable
["id","created_at","updated_at"].include?(k)
end
end
let(:invalid_attributes) do
# create an :invalid trait in your factory so that
# you don't have to worry about the model
FactoryGirl.build(singular, :invalid).attributes.delete_if do |k,_|
["id","created_at","updated_at"].include?(k)
end
end
####
end
RSpec.describe QuotesController, :focus, :type => :controller do
login_admin
it_behaves_like 'a CRUD Controller', model: Quote
end
Finally you are going to find that using a memoized let! will help drastically since you are creating an extraordinary amount of records in those tests as it stands now. This will degrade performance drastically and if you get to a model that has certain globally unique attributes your tests will fail everywhere.
Hopefully this helps start pointing you in the right direction
Update to control testing actions
shared_examples_for 'a CRUD Controller' do |model:|
accessible_method = ->(meth) { public_methods.include?(meth) }
context "GET #index", if: controller.method_defined?(:index) do
it_behaves_like 'authenticatable', view: :index
####
end
context "GET #show", if: controller.method_defined?(:show) do
it_behaves_like 'authenticatable', view: :show, id: record
####
end
end
Here is the improved code (based on engineersmnky's suggestions). Any suggestions for further improvements are welcome.
Controller spec:
# spec/controllers/quotes_controller_spec.rb
require "rails_helper"
RSpec.describe QuotesController, :type => :controller do
it_behaves_like "a CRUD controller",
model: Quote,
create_redirect_path_helper: "quote_path(assigns(:quote))",
update_redirect_path_helper: "quote_url",
delete_redirect_path_helper: "quotes_url"
end
Shared examples:
# spec/support/shared_examples/controller_restful_actions.rb
RSpec.shared_examples "a CRUD controller" do |model:,
create_redirect_path_helper:,
update_redirect_path_helper:,
delete_redirect_path_helper:|
def self.controller_has_action?(action)
described_class.action_methods.include?(action.to_s)
end
resource_singular = model.name.underscore.to_sym
resource_plural = model.name.pluralize.underscore.to_sym
before(:each) { login_admin }
let(:record) { FactoryGirl.create(resource_singular) }
let(:records) { FactoryGirl.create_pair(resource_singular) }
# Models that validate the presence of associated records require some
# hacking in the factory to include associations in the attributes_for output.
let(:valid_attributes) { FactoryGirl.attributes_for(resource_singular) }
# All factories must have a trait called :invalid
let(:invalid_attributes) do
FactoryGirl.attributes_for(resource_singular, :invalid)
end
describe "GET #index", if: controller_has_action?(:index) do
it "requires login" do
logout
get :index
expect(response).to require_login_web
end
it "enforces authorization" do
get :index
expect(controller).to enforce_authorization
end
it "populates ##{resource_plural} with an array of #{resource_plural}" do
# Force records to be created before the request.
records
get :index
# Required when testing the User model, or else the user created
# by the Devise login helper skews the result of this test.
expected_records = assigns(resource_plural) - [#current_user]
expect(expected_records).to match_array(records)
end
end
describe "GET #show", if: controller_has_action?(:show) do
it "requires login" do
logout
get :show, id: record
expect(response).to require_login_web
end
it "enforces authorization" do
get :show, id: record
expect(controller).to enforce_authorization
end
it "assigns the requested #{resource_singular} to an instance variable" do
get :show, id: record
expect(assigns(resource_singular)).to eq(record)
end
end
describe "GET #new", if: controller_has_action?(:new) do
it "requires login" do
logout
get :new
expect(response).to require_login_web
end
it "enforces authorization" do
get :new
expect(controller).to enforce_authorization
end
it "assigns a new #{resource_singular} to ##{resource_singular}" do
get :new
expect(assigns(resource_singular)).to be_a_new(model)
end
end
describe "GET #edit", if: controller_has_action?(:edit) do
it "requires login" do
logout
get :edit, id: record
expect(response).to require_login_web
end
it "enforces authorization" do
get :edit, id: record
expect(controller).to enforce_authorization
end
it "assigns #{resource_singular} to ##{resource_singular}" do
get :edit, id: record
expect(assigns(resource_singular)).to eq(record)
end
end
describe "POST #create", if: controller_has_action?(:create) do
it "requires login" do
logout
post :create, { resource_singular => valid_attributes }
expect(response).to require_login_web
end
it "enforces authorization" do
post :create, { resource_singular => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "saves the new #{resource_singular} in the database" do
expect{
post :create, { resource_singular => valid_attributes }
}.to change(model, :count).by(1)
end
it "assigns the saved #{resource_singular} to ##{resource_singular}" do
post :create, { resource_singular => valid_attributes }
expect(assigns(resource_singular)).to be_an_instance_of(model)
expect(assigns(resource_singular)).to be_persisted
end
it "redirects to #{create_redirect_path_helper}" do
post :create, { resource_singular => valid_attributes }
expect(response).to redirect_to(eval(create_redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not save the new #{resource_singular} in the database" do
expect{
post :create, { resource_singular => invalid_attributes }
}.not_to change(model, :count)
end
it "assigns the unsaved #{resource_singular} to ##{resource_singular}" do
post :create, { resource_singular => invalid_attributes }
expect(assigns(resource_singular)).to be_a_new(model)
end
it "re-renders the :new template" do
post :create, { resource_singular => invalid_attributes }
expect(response).to render_template(:new)
end
end
end
describe "PATCH #update", if: controller_has_action?(:update) do
it "requires login" do
logout
patch :update, { :id => record,
resource_singular => valid_attributes }
expect(response).to require_login_web
end
it "enforces authorization" do
patch :update, { :id => record,
resource_singular => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "updates the requested #{resource_singular}" do
patch :update, { :id => record,
resource_singular => valid_attributes }
record.reload
# Required when testing Devise's User model with reconfirmable on
record.try(:confirm)
expect(record).to have_attributes(valid_attributes)
end
it "assigns the #{resource_singular} to ##{resource_singular}" do
put :update, { :id => record,
resource_singular => valid_attributes }
expect(assigns(resource_singular)).to eq(record)
end
it "redirects to #{update_redirect_path_helper}" do
patch :update, { :id => record,
resource_singular => valid_attributes }
expect(response).to redirect_to(eval(update_redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not update the #{resource_singular}" do
# Do not attempt to "refactor" the following to any of the following:
# not_to change { quote }
# not_to change { quote.attributes }
# not_to have_attributes(invalid_attributes)
# None of the above will work. See
# https://github.com/rspec/rspec-expectations/issues/996#issuecomment-310729685
expect {
patch :update, { :id => record,
resource_singular => invalid_attributes }
}.not_to change { record.reload.attributes }
end
it "assigns the #{resource_singular} to ##{resource_singular}" do
patch :update, { :id => record,
resource_singular => invalid_attributes }
expect(assigns(resource_singular)).to eq(record)
end
it "re-renders the :edit template" do
patch :update, { :id => record,
resource_singular => invalid_attributes }
expect(response).to render_template(:edit)
end
end
end
describe "DELETE #destroy", if: controller_has_action?(:destroy) do
it "requires login" do
logout
delete :destroy, id: record
expect(response).to require_login_web
end
it "enforces authorization" do
delete :destroy, id: record
expect(controller).to enforce_authorization
end
it "deletes the #{resource_singular}" do
# Force record to be created before the `expect` block.
# Otherwise, it is both created and deleted INSIDE the block, causing the
# count not to change.
record
expect{
delete :destroy, id: record
}.to change(model, :count).by(-1)
end
it "redirects to #{delete_redirect_path_helper}" do
delete :destroy, id: record
expect(response).to redirect_to(eval(delete_redirect_path_helper))
end
end
end
For the let blocks, does it not work if you pass in the model as a parameter to the shared example like you do with the redirect_path_helper?
include_examples "GET #index", Quote
and then in your shared_example you can use the record_name method to create record and records from FactoryGirl and generate valid_attributes and invalid_attributes (you could create an :invalid_quote factory as well for invalid attributes, not sure if that's considered a good practice/idea with FactoryGirl though) from there.
For the second problem, you don't need to use the named route helpers, url_for(controller: :quote) and url_for(#quote) should both work.

How to use data from one factory in another factory

I'm trying to test my app so that when a user votes for a certain kind of food, both the count of user.votes goes up as well as food.votes. As of now I can't that to happen, and I'm not sure if I've written my tests wrong or have set up a factory that will not make it possible for the tests to pass. It works when I test manually in the console, so I know that it does function, but the tests do not agree.
FactoryGirl.define do
factory :vote do |f|
f.user_id user
f.food_id food
end
end
def create
#vote = Vote.new(vote_params)
if #vote.save
flash[:notice] = "Thanks for voting!"
else
flash[:notice] = "Something went wrong"
end
end
before(:each) do
#user = create(:user)
sign_in(#user)
#food = create(:food)
end
describe "POST #create" do
context "with valid attributes" do
it "creates a new vote" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(Vote, :count).by(1)
end
it "Updates #user vote count" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
end
it "Updates #food vote count" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
end
end
end
Failures:
1) VotesController POST #create with valid attributes Updates #user vote count
Failure/Error: expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/votes_controller_spec.rb:27:in `block (4 levels) in <top (required)>'
2) VotesController POST #create with valid attributes Updates #food vote count
Failure/Error: expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/votes_controller_spec.rb:32:in `block (4 levels) in <top (required)>'
Finished in 2.99 seconds (files took 4.66 seconds to load)
36 examples, 2 failures
Failed examples:
rspec ./spec/controllers/votes_controller_spec.rb:25 # VotesController POST #create with valid attributes Updates #user vote count
rspec ./spec/controllers/votes_controller_spec.rb:30 # VotesController POST #create with valid attributes Updates #food vote count
Ok, confident enough to take a guess. The issue, I think, is that vote_params is coming from a factory (probably with hard-coded values for user_id/food_id) - but not the real #user and #food that you are testing against. You need to put these values into your vote_params can I suggest something like the following:
context "with valid attributes" do
# Set vote-params from factory, then merge in the #user/#food ids
let(:vote_params) { FactoryGirl.attributes_for(:vote).merge(:user_id => #user.id, :vote_id => #vote.id) }
it "creates a new vote" do
expect { post :create, params: { vote: vote_params } }.to change(Vote, :count).by(1)
end
it "Updates #user vote count" do
expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
end
it "Updates #food vote count" do
expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
end
end

Rspec tests failing with unexpected message :articles

I'm using Devise with Rspec. So I've two model called User and Article which is article belongs_to the user
When run rspec, i got an error which is say:
1) ArticlesController POST #create with valid attributes saves the article
Failure/Error: post :create, { article: valid_attributes }
#<Double "user"> received unexpected message :articles with (no args)
# ./app/controllers/articles_controller.rb:17:in `create'
# ./spec/controllers/articles_controller_spec.rb:43:in `block (4 levels) in <top (required)>'
Below is my articles_controller.rb
def new
#article ||= Article.new
render
end
def create
#article = #user.articles.new(article_params)
if #article.save
redirect_to articles_path, notice: "Well done brah! Your article has been publish"
else
render 'new'
end
end
spec/controllers/articles_controller_spec.rb
RSpec.describe ArticlesController, type: :controller do
let(:article) { FactoryGirl.create(:article) }
let (:valid_attributes) { FactoryGirl.attributes_for(:article) }
describe "POST #create" do
context "with valid attributes" do
it "saves the article" do
sign_in
post :create, { article: valid_attributes }
expect(Article.count).to eq(1)
end
end
end
spec/factories/articles.rb
FactoryGirl.define do
factory :article do
title "Article Title"
content "Article Content"
default_image "default_image"
user
category
end
end
Where is my mistake? I stuck here
UPDATED
spec/rails_helper.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.include ControllerHelpers, :type => :controller
end
spec/support/controller_helpers.rb
module ControllerHelpers
def sign_in(user = double('user'))
if user.nil?
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
allow(controller).to receive(:current_user).and_return(nil)
else
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
end
end
Both of the updated file above i got it from DEvise wiki - https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs
You need to use an actual database record for your user*.
RSpec.describe ArticlesController, type: :controller do
let(:article) { FactoryGirl.create(:article) }
let(:valid_attributes) { FactoryGirl.attributes_for(:article) }
let(:user) { FactoryGirl.create(:user) }
let(:valid_session) { sign_in(user) }
describe "POST #create" do
before { valid_session }
context "with valid attributes" do
it "saves the article" do
# less prone to false positives
expect do
post :create, { article: valid_attributes }
end.to change(Article, :count).by(1)
end
end
end
end
We use expect {}.to change since you can get a false positive if the database is not cleaned out properly.
Devise::TestHelpers already has a sign_in function. So get rid of your ControllerHelpers module so that your project is not linked to the Devise or Warden internals.

Scaffold generated rspec controller test returns nil on Factory created model

Even when the following line works just fine on the model test:
game = FactoryGirl.create(:game)
It doesn't seem to do it on games_controller_rspec.rb.
describe "GET index" do
it "assigns all games as #games" do
game = FactoryGirl.create(:game)
get :index, {}
expect(assigns(:games)).to eq([game])
end
end
And I keep getting "expected: [...]
got: nil"
This is the factory:
FactoryGirl.define do
factory :game do |f|
f.team_a_id { 1 }
f.team_b_id { 2 }
end
end
Full games_controller.rb:
class GamesController < ApplicationController
before_action :set_game, only: [:show, :edit, :update, :destroy]
before_filter :check_admin_status, only: [:new, :edit, :create, :update, :destroy]
def index
#games = Game.all
end
def show
end
def new
#game = Game.new
end
def edit
end
def create
#game = Game.new(game_params)
respond_to do |format|
if #game.save
format.html { redirect_to #game, notice: 'Game was successfully created.' }
format.json { render action: 'show', status: :created, location: #game }
else
format.html { render action: 'new' }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #game.update(game_params)
format.html { redirect_to #game, notice: 'Game was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
def destroy
#game.destroy
respond_to do |format|
format.html { redirect_to games_url }
format.json { head :no_content }
end
end
private
def set_game
#game = Game.find(params[:id])
end
def game_params
params.require(:game).permit(:team_a_id, :team_b_id)
end
end
Full games_controller_spec.rb:
require 'spec_helper'
describe GamesController do
include Devise::TestHelpers
let(:valid_attributes) { { "team_a_id" => "1" } }
let(:valid_session) { {} }
describe "GET index" do
it "assigns all games as #games" do
game = FactoryGirl.create(:game)
get :index, {}
expect(assigns(:games)).to eq([game])
end
end
describe "GET show" do
it "assigns the requested game as #game" do
game = FactoryGirl.create(:game)
get :show, {:id => game.to_param}, valid_session
expect(assigns(:game)).to eq(game)
end
end
describe "GET new" do
it "assigns a new game as #game" do
get :new, {}, valid_session
expect(assigns(:game)).to be_a_new(Game)
end
end
describe "GET edit" do
it "assigns the requested game as #game" do
game = FactoryGirl.create(:game)
get :edit, {:id => game.to_param}, valid_session
expect(assigns(:game)).to eq(game)
end
end
describe "POST create" do
describe "with valid params" do
it "creates a new Game" do
expect {
post :create, {:game => valid_attributes}, valid_session
}.to change(Game, :count).by(1)
end
it "assigns a newly created game as #game" do
post :create, {:game => valid_attributes}, valid_session
expect(assigns(:game)).to be_a(Game)
expect(assigns(:game)).to be_persisted
end
it "redirects to the created game" do
post :create, {:game => valid_attributes}, valid_session
expect(response).to redirect_to(Game.last)
end
end
describe "with invalid params" do
it "assigns a newly created but unsaved game as #game" do
allow_any_instance_of(Game).to receive(:save).and_return(false)
post :create, {:game => { "team_a_id" => "invalid value" }}, valid_session
expect(assigns(:game)).to be_a_new(Game)
end
it "re-renders the 'new' template" do
allow_any_instance_of(Game).to receive(:save).and_return(false)
post :create, {:game => { "team_a_id" => "invalid value" }}, valid_session
expect(response).to render_template("new")
end
end
end
describe "PUT update" do
describe "with valid params" do
it "updates the requested game" do
game = Game.create! valid_attributes
expect_any_instance_of(Game).to receive(:update).with({ "team_a_id" => "1" })
put :update, {:id => game.to_param, :game => { "team_a_id" => "1" }}, valid_session
end
it "assigns the requested game as #game" do
game = Game.create! valid_attributes
put :update, {:id => game.to_param, :game => valid_attributes}, valid_session
expect(assigns(:game)).to eq(game)
end
it "redirects to the game" do
game = Game.create! valid_attributes
put :update, {:id => game.to_param, :game => valid_attributes}, valid_session
expect(response).to redirect_to(game)
end
end
describe "with invalid params" do
it "assigns the game as #game" do
game = Game.create! valid_attributes
allow_any_instance_of(Game).to receive(:save).and_return(false)
put :update, {:id => game.to_param, :game => { "team_a_id" => "invalid value" }}, valid_session
expect(assigns(:game)).to eq(game)
end
it "re-renders the 'edit' template" do
game = Game.create! valid_attributes
allow_any_instance_of(Game).to receive(:save).and_return(false)
put :update, {:id => game.to_param, :game => { "team_a_id" => "invalid value" }}, valid_session
expect(response).to render_template("edit")
end
end
end
describe "DELETE destroy" do
it "destroys the requested game" do
game = Game.create! valid_attributes
expect {
delete :destroy, {:id => game.to_param}, valid_session
}.to change(Game, :count).by(-1)
end
it "redirects to the games list" do
game = Game.create! valid_attributes
delete :destroy, {:id => game.to_param}, valid_session
expect(response).to redirect_to(games_url)
end
end
end
Found it. The problem was with user privileges. I'm using devise and only allow certain methods to regular users. Therefore the result for them would be nil, unless i take the test with an admin user.
Basically I had to set an admin user on spec to make it work.
This is how you do it:
1) Write controller_macros.rb inside spec/support
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
user.confirm!
sign_in user
end
end
end
2) Add it to spec_helper
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
3) Set the user to admin on your controller_spec
describe GamesController do
login_admin
describe "GET index" do
game = FactoryGirl.create(:game)
get :index, {}, valid_session
expect(assigns(:games).to eq([game])
end
My User factory:
FactoryGirl.define do
factory :user do
email "nn#nnn.com"
password "12345654321"
password_confirmation { "12345654321" }
factory :admin do
after(:create) { |user| user.update_attribute :admin, true }
end
end
end
Founded here.
I'm having the same exact issue. I found a post on here (I forgot to bookmark it and am having trouble finding it) that said to replace your "get :index, {}" with "controller.index" and see if it works. It worked for me when I did this. They also said it was a routing issue. What I don't understand is why, and how to fix it so that I can use the standard code that's generated from the scaffold. My route tests pass, but yet there's a routing issue that's causing "get" not to work in these tests? I don't understand.
EDIT:
Just found the post. Check it out here. The OP never comes back to tell what the issue was our how they fixed it though. I would love to know as I'm frustrated trying to figure out how to get this working.

Resources