Wrong values in the parameters passed by rspec - ruby-on-rails

I am trying to test a service but something wrong is happening when I pass the parameters to the service class, the values ​​are being passed in the wrong way by rspec.
My service is:
class CheckInvitesService
def initialize(user, course)
#user = user
#course = course
end
def call
if UserCourseRegistration.exists?(user_id: #user, course_id: #course)
false
else
UserCourseRegistration.create(user_id: #user,
course_id: #course,
school_id: find_school)
end
end
private
def find_school
school = Course.find(#course).school.id
end
end
My test is:
require 'rails_helper'
RSpec.describe CheckInvitesService do
describe "call" do
context 'invite already exists' do
it 'return' do
#current_user_admin = create(:admin)
#school = create(:school, user: #current_user_admin)
#course = create(:course, user: #current_user_admin, school: #school)
# puts #course
# puts #course.id
#verify = CheckInvitesService.new(#course.id, #current_user_admin.id).call
expect(#verify).to be_falsey
end
end
end
end
I printed #course.id and it returns: 122
But when I call the service class, the parameter inside it has another value, for example the #course.id, i passed takes the value: 627
I get the following error:
Failures:
1) CheckInvitesService call invite already exists return
Failure/Error: school = Course.find(#course).school.id
ActiveRecord::RecordNotFound:
Couldn't find Course with 'id'=627
What is entering the class is another id of course the id 627 and not the 122 that should have been passed via parameter.

It appears your arguments are out of order. CheckInvitesService has:
initialize(user, course)
But when you create the CheckInvitesService object, you're passing course as the first argument.
CheckInvitesService.new(#course.id, #current_user_admin.id).call
Should be
CheckInvitesService.new(#current_user_admin.id, #course.id).call

You can use find_or_create_by in your service, it should work, too.
def call
UserCourseRegistration.find_or_create_by(user: #user, course: #course, school: find_school)
end

Related

ActiveRecord decrement not working in test

In my Rails 5.1.6 application I created an Attendance model association between users and events so that users can participate to a given event. Events have an integer seats attribute that is decremented each time a participation is created. The Attendance controller is:
before_action :logged_in_user
before_action :participants_limit, only: :create
def create
#attendance = current_user.attendances.build(attendance_params)
if #attendance.save
#event.decrement(:seats)
flash[:success] = "Congratulation! Your participation has been recorded."
redirect_back(fallback_location: root_url)
else
flash[:danger] = #event.errors.full_messages.join(', ')
redirect_back(fallback_location: root_url)
end
end
private
def attendance_params
params.require(:attendance).permit(:event_id)
end
def participants_limit
#event = Event.find(params[:attendance][:event_id])
if #event.seats == 0
flash[:danger] = 'Attention! Unfortunately this event is full!'
redirect_back(fallback_location: root_url)
end
end
I would like to test the create action of the Attendance controller, so I created the following test:
def setup
#event = events(:event_1)
#user = users(:user_12)
end
test "should redirect create when participants limit reached" do
assert #event.participants.count == 2
assert #event.seats == 5
participants = (1..5).map do |num|
users("user_#{num}".to_sym)
end
participants.each do |user|
log_in_as(user)
post attendances_path, params: { attendance: { user_id: user.id, event_id: #event.id } }
end
assert #event.participants.count == 7
assert #event.seats == 0
log_in_as(#user)
assert_no_difference 'Attendance.count' do
post attendances_path, params: { attendance: { user_id: #user.id, event_id: #event.id } }
end
assert_redirected_to root_url
end
The test fails at the line assert #event.seats == 0: seats are not decremented and result still 5: why is that? I wonder why the decrement method is not working, and if there is something am I missing in the test.
Your #event is cached in memory and controllers change corresponding database record.
use #record.reload.seats in tests when you expect it to have just changed - this reloads record from database.
Also keep in mind that your code is vulnerable to a race condition if two participants will try to register at the same time, wrap whole check-change code block in event.with_lock to mitigate that.

Test strong parameters with minitest in Rails 4

I am new to Rails and I want to test my set strong parameters of the Book model with a controller test. I am using Minitest and Rails 4.
Book model:
class Book < ActiveRecord::Base
validates :title, presence: true, length: { in: 1..150 }
end
Book controller wit params:
def create
#book = Book.new book_params
if #book.save
redirect_to action: "index", notice: 'Success.'
else
render :new
end
end
private
def book_params
params.require(:book).permit(:title, :cover_image)
end
My idea for a test - does fail, because it creates an entry:
assert_no_difference('Book.count') do
post :create, book: {
id: 123,
title: "Lorem ipsum"
}
end
How can I get the tests go green and is it correct to test the strong parameters with a controller test?
I am looking for an answer to almost the same question. When using Rails 5 I eventually came up with a solution (call it workaround if you like :-) for testing that the unwanted params don't actually get through. In my (simplified here) case I want to disallow some "security critical" params being passed through when creating a new user.
In the controller (permitting only email and password):
private
def user_params
params.require(:user).permit(:email, :password)
end
In the integration test:
test "not permitted signup data submitted" do
new_user_email = "tester_" + (0...10).map { ('0'..'9').to_a[rand(26)] }.join + "#testing.net"
get signup_path
assert_difference 'User.count', 1 do
post signup_path, params: { user: { email: new_user_email, password: "testpassword", role_id: 1 } }
end
user = User.last
assert user.email == new_user_email
assert user.role_id == nil
end
Here I submit an additional, "sensitive" parameter role_id with the value of 1 (admin). I expect the user to be created. Then I read that newly (last) created user and expect it to have role_id empty (nil). To make the test fail I add :role_id to user_params. Removing it, makes the test pass. Obviously if your attribute can't be nil (aka NULL in SQL), you can test for default value being stored instead of the submitted one.
Since Rails drops all unpermitted parameters not in permit, the new record will be created, hence the test will be red.
Although, one can raise an exception with the action_on_unpermitted_parameters method when non-whitlisted parameters are submitted.
I do like to test Strong Parameters in the controller. I also like to test them more directly, so this is how I do it.
First, I have a test helper that is required in my test/test_helper.rb file:
test/test_helpers/controller_strong_params_helper.rb
# frozen_string_literal: true
module ControllerStrongParamsHelper
def assert_requires_param(param, &block)
#controller.params = ActionController::Parameters.new()
assert_raises(ActionController::ParameterMissing) { yield }
#controller.params = ActionController::Parameters.new(stub_parameter: {})
assert_raises(ActionController::ParameterMissing) { yield }
# It's not enough to have an empty required parameter, there needs to be something inside.
#controller.params = ActionController::Parameters.new(param => {})
assert_raises(ActionController::ParameterMissing) { yield }
#controller.params = ActionController::Parameters.new(param => '')
assert_raises(ActionController::ParameterMissing) { yield }
#controller.params = ActionController::Parameters.new(param => {something_inside: 'something'})
assert_nothing_raised { yield }
end
end
This lets me easily test the strong params that are not optional.
Now assume I have these strong params in my ExampleController:
def example_params
params.require(:example).permit(:id,
:name,
:description)
end
private :example_params
This is what my minitest tests would look like:
test/controllers/example_controller_test.rb
###############################################
test '#example_params should require an example parameter' do
assert_requires_param(:example) { #controller.send(:example_params) }
end
###############################################
test '#example_params should permit some expected example parameters' do
# Using hash rockets so the equality check works.
expected_permitted_params = { 'id' => nil,
'name' => nil,
'description' => nil }
# Specifically merge in any potential non-attribute parameters here if present/needed.
all_params = { example: Example.new.attributes }
#controller.params = ActionController::Parameters.new(all_params)
actual_permitted_params = #controller.send(:example_params)
assert_equal(expected_permitted_params, actual_permitted_params)
end

Testing letsrate controller. RSpec

I want to test letsrate generated controller.
But I don;t know how do this, because I can not understand how it works.
rater_controller.rb
class RaterController < ApplicationController
def create
if user_signed_in?
obj = params[:klass].classify.constantize.find(params[:id])
obj.rate params[:score].to_i, current_user, params[:dimension]
render :json => true
else
render :json => false
end
end
end
UPDATE
Letsrate is a gem for rails
rater_controller_spec.rb
require 'rails_helper'
describe RaterController do
describe 'POST create' do
let(:valid_attributes)do {
klass: 'Hotel',
dimension: 'rating',
score: '5'
}
end
it 'user signed in' do
user = create(:user)
hotel = create(:hotel)
post :create, { rate: valid_attributes, rater_id: user.id, rateble_id: hotel.id }
sign_in user
end
end
end
The source code you posted makes it pretty obvious how it works. You need to call the create action in RaterController with these params: klass, id, score, dimension. Let's say the klass param is "Restaurant", which is also the name of an ActiveRecord model class. The controller will query the database for a restaurant with the specified ID. Then it will call the rate method on that object with the specified parameters, which presumably inserts a row into the database representing the user's new rating. To test it, you could simply call the controller action and then check to make sure the row got added to the database.

RSpec with multi tenancy - Why is this simple test failing?

What I'm doing
I recently implemented multi-tenancy (using scopes) following Multitenancy with Scopes (subscription required) as a guide. NOTE: I am using the dreaded "default_scope" for tenant scoping (as shown in Ryan's Railscast). Everything is working in browser just fine, but many (not all) of my tests are failing and I can't figure out why.
I built authentication from scratch (based on this Railscast: Authentication from Scratch (revised) - subscription required) and using an auth_token for "Remember me" functionality (based on this Railscast: Remember Me & Reset Password).
My question
Why is this test failing, and why do the two workarounds work? I've been stumped for a couple days now and just can't figure it out.
What I think is happening
I'm calling the Jobs#create action, and the Job.count is reducing by 1 instead of increasing by 1. I think what's happening is the job is being created, then the app is losing the 'tenant' assignment (tenant is dropping to nil), and the test is counting Jobs for the wrong tenant.
What's odd is that it's expecting "1" and getting "-1" (and not "0"), which implies it's getting a count (note that there's already a 'seed' job created in the before block, so it's probably counting "1" before calling #create), calling the create action (which should increase the count by 1 to 2 total), then losing the tenant and switching to a nil tenant where there are 0 jobs. So it:
Counts 1 (seed job)
Creates a job
Loses the tenant
Counts 0 jobs in the new (probably nil) tenant
...resulting in a -1 change in the Job.count.
You can see below that I've semi-confirmed this by adding ".unscoped" to my Job.count line in the test. This implies that the expected number of jobs is there, but the jobs just aren't in the tenant the app is testing under.
What I don't understand is how it's losing the tenant.
Code
I've tried to grab the relevant parts of my code, and I've created a dedicated single-test spec to make this as easy to dissect as possible. If I can do anything else to make this easy on possible answerers, just let me know what to do!
# application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
around_filter :scope_current_tenant
private
def current_user
#current_user ||= User.unscoped.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
helper_method :current_user
def current_tenant
#current_tenant ||= Tenant.find_by_id!(session[:tenant_id]) if session[:tenant_id]
end
helper_method :current_tenant
def update_current_tenant
Tenant.current_id = current_tenant.id if current_tenant
end
helper_method :set_current_tenant
def scope_current_tenant
update_current_tenant
yield
ensure
Tenant.current_id = nil
end
end
# sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.unscoped.authenticate(params[:session][:email], params[:session][:password])
if user && user.active? && user.active_tenants.any?
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
if !user.default_tenant_id.nil? && (default_tenant = Tenant.find(user.default_tenant_id)) && default_tenant.active
# The user has a default tenant set, and that tenant is active
session[:tenant_id] = default_tenant.id
else
# The user doesn't have a default
session[:tenant_id] = user.active_tenants.first.id
end
redirect_back_or root_path
else
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
end
end
def destroy
cookies.delete(:auth_token)
session[:tenant_id] = nil
redirect_to root_path
end
end
# jobs_controller.rb
class JobsController < ApplicationController
before_filter :authenticate_admin
# POST /jobs
# POST /jobs.json
def create
#job = Job.new(params[:job])
#job.creator = current_user
respond_to do |format|
if #job.save
format.html { redirect_to #job, notice: 'Job successfully created.' }
format.json { render json: #job, status: :created, location: #job }
else
flash.now[:error] = 'There was a problem creating the Job.'
format.html { render action: "new" }
format.json { render json: #job.errors, status: :unprocessable_entity }
end
end
end
end
# job.rb
class Job < ActiveRecord::Base
has_ancestry
default_scope { where(tenant_id: Tenant.current_id) }
.
.
.
end
# sessions_helper.rb
module SessionsHelper
require 'bcrypt'
def authenticate_admin
deny_access unless admin_signed_in?
end
def deny_access
store_location
redirect_to signin_path, :notice => "Please sign in to access this page."
end
private
def store_location
session[:return_to] = request.fullpath
end
end
# spec_test_helper.rb
module SpecTestHelper
def test_sign_in(user)
request.cookies[:auth_token] = user.auth_token
session[:tenant_id] = user.default_tenant_id
current_user = user
#current_user = user
end
def current_tenant
#current_tenant ||= Tenant.find_by_id!(session[:tenant_id]) if session[:tenant_id]
end
end
# test_jobs_controller_spec.rb
require 'spec_helper'
describe JobsController do
before do
# This is all just setup to support requirements that the admin is an "Admin" (role)
# That there's a tenant for him to use
# That there are some workdays - a basic requirement for the app - jobs, checklist
# All of this is to satisfy assocations and
#role = FactoryGirl.create(:role)
#role.name = "Admin"
#role.save
#tenant1 = FactoryGirl.create(:tenant)
#tenant2 = FactoryGirl.create(:tenant)
#tenant3 = FactoryGirl.create(:tenant)
Tenant.current_id = #tenant1.id
#user = FactoryGirl.create(:user)
#workday1 = FactoryGirl.create(:workday)
#workday1.name = Time.now.to_date.strftime("%A")
#workday1.save
#checklist1 = FactoryGirl.create(:checklist)
#job = FactoryGirl.create(:job)
#checklist1.jobs << #job
#workday1.checklists << #checklist1
#admin1 = FactoryGirl.create(:user)
#admin1.tenants << #tenant1
#admin1.roles << #role
#admin1.default_tenant_id = #tenant1.id
#admin1.pin = ""
#admin1.save!
# This is above in the spec_test_helper.rb code
test_sign_in(#admin1)
end
describe "POST create" do
context "with valid attributes" do
it "creates a new job" do
expect{ # <-- This is line 33 that's mentioned in the failure below
post :create, job: FactoryGirl.attributes_for(:job)
# This will pass if I change the below to Job.unscoped
# OR it will pass if I add Tenant.current_id = #tenant1.id right here.
# But I shouldn't need to do either of those because
# The tenant should be set by the around_filter in application_controller.rb
# And the default_scope for Job should handle scoping
}.to change(Job,:count).by(1)
end
end
end
end
Here is the failure from rspec:
Failures:
1) JobsController POST create with valid attributes creates a new job
Failure/Error: expect{
count should have been changed by 1, but was changed by -1
# ./spec/controllers/test_jobs_controller_spec.rb:33:in `block (4 levels) in <top (required)>'
Finished in 0.66481 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/test_jobs_controller_spec.rb:32 # JobsController POST create with valid attributes creates a new job
If I add some 'puts' lines to see who the current_tenant is directly and by inspecting the session hash, I see the same tenant ID all the way:
describe "POST create" do
context "with valid attributes" do
it "creates a new job" do
expect{
puts current_tenant.id.to_s
puts session[:tenant_id]
post :create, job: FactoryGirl.attributes_for(:job)
puts current_tenant.id.to_s
puts session[:tenant_id]
}.to change(Job,:count).by(1)
end
end
end
Yields...
87
87
87
87
F
Failures:
1) JobsController POST create with valid attributes creates a new job
Failure/Error: expect{
count should have been changed by 1, but was changed by -1
# ./spec/controllers/test_jobs_controller_spec.rb:33:in `block (4 levels) in <top (required)>'
Finished in 0.66581 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/test_jobs_controller_spec.rb:32 # JobsController POST create with valid attributes creates a new job
I think it's not that RSpec is ignoring the default scope but it's reset in the ApplicationController in the around filter by setting the current user to nil.
I encountered this issue with assigns(...) and it happened because the relation is actually resolved when you're evaluating assigns. I think this may also be the case with the expectation in your case.
UPDATE: In my situation, the cleanest solution I could find (though I still hate it) is to let the default scope leak through by not setting the current user to nil in test environment.
In your case this would amount to:
def scope_current_tenant
update_current_tenant
yield
ensure
Tenant.current_id = nil unless Rails.env == 'test'
end
I haven't tested it with your code but maybe this will help.
I managed to get my tests to pass, although I'm still not sure why they were failing to begin with. Here's what I did:
describe "POST create" do
context "with valid attributes" do
it "creates a new job" do
expect{ # <-- This is line 33 that's mentioned in the failure below
post :create, job: FactoryGirl.attributes_for(:job)
}.to change(Job.where(tenant_id: #tenant1.id),:count).by(1)
end
end
end
I changed:
change(Job,:count).by(1)
...to:
change(Job.where(tenant_id: #tenant1.id),:count).by(1)
NOTE: #tenant1 is the logged-in admin's tenant.
I assumed default_scopes would be applied in RSpec, but it seems they aren't (or at least not in the ":change" portion of an "expect" block). In this case, the default_scope for Job is:
default_scope { where(tenant_id: Tenant.current_id) }
In fact, if I change that line to:
change(Job.where(tenant_id: Tenant.current_id),:count).by(1)
...it will also pass. So if I explicitly mimic the default_scope for Job within the spec, it'll pass. This seems like confirmation that RSpec is ignoring my default_scope on Jobs.
In a way, I think my new test is a better way to make sure tenant data stays segregated because I'm explicitly checking counts within a particular tenant rather than implicitly checking the counts for a tenant (by assuming the count is in the "current tenant").
I'm marking my answer is correct because it's the only answer, and if someone else encounters this, I think my answer will help them get past the issue. That said, I really haven't answered my original question regarding why the test was failing. If anyone has any insight into why RSpec seems to be ignoring default_scope in "expect" blocks, that might help making this question useful for others.
I have the same issue of you guys. I didn't resolve in a way that makes me comfortable but is still better than verifying your RAILS_ENV. Take this example.
it "saves person" do
expect {
some_post_action
}.to change(Person, :count).by(1)
end
Every time i try to save the count method makes a select like:
"select count(*) from persons where tenant_id is null"
I manage to resolve this issue by setting Person.unscoped in the change method i changed this:
}.to change(Person, :count).by(1)
to this:
}.to change(Person.unscoped, :count).by(1)
It's not the best solution but i'm still trying to find a way to get around the default_scope.

How to test a controller with steps to use some action

In my system, I have a user that have one company that have multiple accounts.
User sign in system using Devise, and have a virtual attribute called selected_company that was setted in CompaniesController.
I want to make multiple tests in AccountsController with this scenario.
I have this code to sign_in user, this code works well:
before :each do
#user = create(:user)
#user.confirm!
sign_in #user
end
But I must to have a specific context that I tried to code as:
context 'when user already selected a company' do
before :each do
#company = create(:company)
#account = create(:account)
#company.accounts << #account
#user.selected_company = #company
end
it "GET #index must assings #accounts with selected_company.accounts" do
get :index
expect(assigns(accounts)).to match_array [#account]
end
end
But this code won't work, when I run it I got this error:
undefined method `accounts' for nil:NilClass
My AccountsController#index have only this code:
def index
#accounts = current_user.selected_company.accounts
end
I'm new in rspec and TDD and I have some time to test everything I want, and I want to test everything to practice rspec.
I don't know if this is the best way to test this things, so I'm open to suggestions.
Replace with:
expect(assigns(:accounts)).to match_array [#accounts]
Note, :accounts instead of just account.
Also, as I see it, you don't have #accounts in your spec. Please declare that, too. :)
Probably you are not saving selected_company and when you call this on your controller it returns nil.
Try save #user.save after set selected_company:
context 'when user already selected a company' do
before :each do
#company = create(:company)
#account = create(:account)
#company.accounts << #account
#user.selected_company = #company
#user.save
end
it "GET #index must assings #accounts with selected_company.accounts" do
get :index
expect(assigns(accounts)).to match_array [#account]
end
end
Hope to help you.
Finaly, I found the problem!
I changed the before statement to:
before :each do
#company = create(:company)
#account = create(:account)
#company.accounts << #account
controller.current_user.selected_company = #company
end
And changed assigns(accounts) to assings(:accounts) (with symbol) in expect method.

Resources