DRY rspec syntax for controller specs - ruby

Typically in my controller specs I'll do something like this:
describe MyController do
describe 'POST #create' do
let!(:my_model) { initialize_something_here }
before :each do
post :create, my_model: my_model
end
it 'should be successful' do
response.should be_successful
end
... more tests ...
end
end
My question is, when I do assertions that use an expect block such as checking that the count of items in the database is incremented after a create, I have to remove the post call from the before :each block, like this and repeat it for each it statement:
describe MyController do
describe 'POST #create' do
let!(:my_model) { initialize_something_here }
it 'should insert into database' do
expect { post :create, my_model: my_model }.to change(MyModel, :count).by(1)
end
it 'should be successful' do
post :create, my_model: my_model
response.should be_successful
end
... more tests ...
end
end
Is there a DRY-er way to do the post call?

I believe you can use a lambda for that maybe in a let
let(:post_create_model) { -> { post :create, my_model }}
it 'should insert into database' do
expect(post_create_model).to change(MyModel, :count).by(1)
end
it 'should be successful' do
post_create_model.call
response.should be_successful
end

Related

Stubbing out an instance of a class to test a callback given as an attribute

I'm attempting to test a line of code in a proc see (proc { |message| flash[:notice] << message } in the code snippet below) using Rspec 3.9 but I can't seem to stub out the instance to do what I want.
Given the following controller and test, how can I stub out CreateAccount and run the on_success attribute given in the controller?
Here is the controller file
class AccountsController < ApplicationController
def create
CreateAccount.new(
on_success: proc { |message| flash[:notice] << message }
).process
redirect_to action: :index
end
end
Here is the Rspec test file
describe AccountsController, type: :controller do
describe 'POST #create' do
subject(:create_action) { post :create, id: 1 }
let(:success_message) { 'Success!' }
context 'when created account successfully' do
it { is_expected.to redirect_to action: :index }
it do
create_action
expect(flash[:notice]).to include success_message
end
end
end
end
The reason I want to do this is to separate concerns from the controller to the CreateAccount object. It shouldn't matter, but here is the CreateAccount object so far.
class CreateAccount
def initialize on_success: proc { |_| }
#on_success = on_success
end
def call
# Do some meaningful work
success_message = 'Meaningful message'
#on_success.call(success_message)
end
end
I've managed to find 1 solution that works but I would like to know if there's a cleaner way
Note: The controller and CreateAction classes remain the same.
describe AccountsController, type: :controller do
describe 'POST #create' do
subject(:create_action) { post :create, id: 1 }
context 'when created account successfully' do
let(:dummy_class) do
Class.new(CreateAction) do
def call
#on_success.call(self.class.message)
end
def self.message
'Success!'
end
end
end
before { stub_constant 'CreateAction', dummy_class }
it { is_expected.to redirect_to action: :index }
it do
create_action
expect(flash[:notice]).to include dummy_class.message
end
end
end
end

Rails 4 function in controller not running

I have some code for a Rails 4 project I'm working on. It uses active_record (mysql2), and there is a has_many :through relationship that works properly when I interact through rails c (in either production or development). When I try to submit the relationship in a form (I am using simple_form), I can't seem to get it to save.
Here is how my information is currently set up (just showing snippets, I can't really show the whole source):
Model:
has_many :categorizations
has_many :resource_categories, through: :categorizations
accepts_nested_attributes_for :resource_categories
accepts_nested_attributes_for :categorizations
Form:
= simple_form_for #resource do |f|
= f.association :resource_categories
Controller:
# POST /resources
# POST /resources.json
def create
#resource = Resource.new(resource_params)
set_categories(#resource, params[:resource][:resource_category_ids])
respond_to do |format|
if #resource.save
format.html {
redirect_to #resource, notice: 'Resource was successfully created.'
}
format.json {
render action: 'show', status: :created, location: #resource
}
else
format.html {
render action: 'new'
}
format.json {
render json: #resource.errors, status: :unprocessable_entity
}
end
end
end
# PATCH/PUT /resources/1
# PATCH/PUT /resources/1.json
def update
respond_to do |format|
if #resource.update(resource_params)
set_categories(#resource, params[:resource][:resource_category_ids])
format.html {
redirect_to #resource, notice: 'Resource was successfully updated.'
}
format.json {
head :no_content
}
else
format.html {
render action: 'edit'
}
format.json {
render json: #resource.errors, status: :unprocessable_entity
}
end
end
end
# Never trust parameters from the scary internet, only allow the white list
# through.
def resource_params
params.require(:resource).permit(
:title, :slug, :ancestry, :status, :author_id, :published, :parent_id,
:resource_category_ids, :preview, :body
)
end
def set_categories(resource, categories)
# Clean out the existing categories (if there are any)
unless resource.resource_categories.blank?
resource.resource_categories.each do |category|
resource.resource_categories.delete(category)
end
end
unless categories.blank?
categories.each do |category|
unless category.blank?
resource.resource_categories << ResourceCategory.find(category)
end
end
end
end
When I issue the following commands using rails c -e production (or just plain rails c) it works (In this example, I assign all categories to all resources):
Resource.all.each do |resource|
ResourceCategory.all.each do |category|
resource.resource_categories << category
end
end
It seems like my problem is that the controller is not calling the helper function
Use this instead:
def create
#resource = Resource.new(resource_params)
#resource.set_categories(params[:resource][:resource_category_ids])
..
end
Move the method in the Resource model:
def set_categories(categories)
# Clean out the existing categories (if there are any)
unless new_record?
unless resource_categories.blank?
resource_categories.each do |category|
resource_categories.delete(category)
end
end
end
unless categories.blank?
categories.each do |category|
unless category.blank?
resource_categories << ResourceCategory.find(category)
end
end
end
end
#resource is instance variable of your Controller, you don't need to pass it to a method. Perform all your operations directly on the instance variable.
OP still had problem while saving the record, changed :resource_category_ids to :resource_category_ids => [] in resource_params method:
def resource_params
params.require(:resource).permit(
:title, :slug, :ancestry, :status, :author_id, :published, :parent_id,
:preview, :body, :resource_category_ids => []
)
end

How can we test /user/show?id=xx with rspec?

The route in my system is:
user_show GET /user/show(.:format) user#show.
The controller code is:
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #user}
end
end
_spec.rb
describe 'GET #show' do
it 'should return success' do
get :show, id:#user.id
expect(response).to be_success
end
end
result is:
Failure/Error: expect(response).to be_success
expected success? to return true, got false
In the browser, when I type xxx/user/show, it get error.
ActiveRecord::RecordNotFound in UserController#show
But if I type xxx/user/show?id=31, it shows user with id=31!!
Thanks for #Alex Wayne, I add more information here: I check the routes.rb file:
get "user/show"
get "user/index"
get "user/delete"
get "user/edit"
post "user/update"
resource :users, :path => :user, :as => :user
I personal think my teammate should not write down "get user/show, get user/index...." based on Rails Routing. But I can't change their code. So,
anyone know how to test user/show?id=xxx ? Many thanks~!!!!

Ruby on Rails RSpec put method doesn't see signed in user

so I've been using Michael Hartl's tutorial for some time and I can say it's really useful but there's a problem and I gues it's not on the tutorial's part. So in chapter "9.2.2 Requiring the right user" ther's a test for checking that a user can access neither other user's edit page nor submit a direct PUT reauest.
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { sign_in user }
describe "visiting Users#edit page" do
before { visit edit_user_path(wrong_user) }
it { should_not have_selector('title', text: full_title('Edit user')) }
end
describe "submitting a PUT request to the Users#update action" do
before { put user_path(wrong_user) }
specify { response.should redirect_to(root_path) }
end
end
So long all seems right but the test fails:
1) Authentication authorization as wrong user submitting a PUT request to the Users#update action ←[31mFailure/Error:←[0m ←[31mspecify { response.should redirect_to(root_path }←[0m←[31mExpected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/signin>←[0m←[36m # ./spec/requests/authentication_pages_spec.rb:107:in `block (5 levels) in <top (required)>'←[0m
Here's the User controller:
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:index, :edit, :update]
before_filter :correct_user, only: [:edit, :update]
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
private
def signed_in_user
unless signed_in?
puts "No user signed in"
store_location
redirect_to signin_path, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
puts "Incorrect user" unless current_user?(#user)
redirect_to(root_path) unless current_user?(#user)
end
end
So as you can see the problem is that when using RSpec put method, the test fails even before checking for the right user because it sees ther's no user signed in.
This is a small problem which can easily be omitted (incorrect user cannot make direct PUT request anyway) but it's a puzzle for me why doesn't it work correct and I can't get the answer for quite a time already.
It looks like the signed_in_user filter is redirecting back to the sign in page before the correct_user fires. That suggests that the user is not actually signed in correctly by the sign_in user call in the before block.
Have you defined sign_in in spec/support/utilities.rb?
include ApplicationHelper
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well.
cookies[:remember_token] = user.remember_token
end

Fail at redirect expectation in a controller spec

I am using Devise 1.4.2, RSpec 2.6.0 and Rails 3.1.0.rc6. My routes.rb looks like this:
scope "(:locale)", :locale => /e(s|n)/ do
resources :demotivideos, :only => [:index, :show]
devise_for :users
namespace "admin" do
resources :demotivideos, :except => [:index, :show]
end
end
I am spec'ing that, when a not logged in user acces new, create or update, he should be redirected to new_user_session_path. For this, I am using the following code
context "when not logged in" do
before(:each) do
sign_out user
end
describe "GET new" do
it "should redirect to new user session" do
get :new
response.should redirect_to(new_user_session_path)
end
end
describe "POST create" do
it "should redirect to new user session" do
post :create, :demotivideo => valid_attributes
response.should redirect_to(new_user_session_path)
end
end
describe "PUT update" do
it "should redirect to new user session" do
put :update, :id => 1, :demotivideo => valid_attributes
response.should redirect_to(new_user_session_path)
end
end
end
All are failing because of the same reason: expected route includes the locale (by default en) but the actual redirect was to the same path without locale. My application controller was modified as told in Rails Guides:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
def default_url_options(options={})
logger.debug "default_url_options is passed options: #{options.inspect}\n"
{ :locale => I18n.locale }
end
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
end
What am I doing wrong?
Seems like though Rails Guides uses def default_url_options in Devise you need def self.default_url_options. Don't know the difference, though.

Resources