Passing a valid attribute to controller but failing validation - ruby-on-rails

Model:
module V1
class Player < ActiveRecord::Base
validates :name, presence: true
validates :default_pull_rate, numericality: true, allow_nil: false
has_many :player_links
end
end
Spec (I even tried explicitly setting the default_pull_rate inline as seen below):
it "creates a new player" do
expect { post :create, format: :json, player: FactoryGirl.attributes_for(:player, default_pull_rate: 5) }.to change(V1::Player, :count).by(1)
end
Factory:
FactoryGirl.define do
factory :player, class: V1::Player do
name "Frank"
default_pull_rate 100
end
Controller:
....
def create
#player = Player.new(player_params)
if #player.save!
redirect_to #player
end
end
private
def player_params
params.require(:player).permit(:name, :default_pull_rated)
end
Error message:
ActiveRecord::RecordInvalid: Validation failed: Default pull rate is not a number
Passing model specs:
it "is invalid without a default_pull_rate" do
expect(FactoryGirl.build(:player, default_pull_rate: nil)).to_not be_valid
end
it "is invalid when default_pull_rate is a string" do
expect(FactoryGirl.build(:player, default_pull_rate: "fast")).to_not be_valid
end

Typo in player_params?
def player_params
params.require(:player).permit(:name, :default_pull_rated)
end
should be _rate not _rated
def player_params
params.require(:player).permit(:name, :default_pull_rate)
end

Related

NoMethodError: undefined method `access_token' for nil:NilClass in RSpec

I am trying make my tests for user authentication pass when the user sends verification code to the Github/Google service to receive a valid token using Octokit gem. I am running at NoMethodError undefined method `access_token' for nil:NilClass in RSpec.
This is screenshot of my terminal:
undefined method `access_token' for nil:NilClass in RSpeca terminal screenshot of the problem
This is my UserAuthenticator lib:
class UserAuthenticator
class AuthenticationError < StandardError; end
attr_reader :user, :access_token
def initialize(code)
#code = code
end
def perform
raise AuthenticationError if code.blank?
raise AuthenticationError if token.try(:error).present?
prepare_user
#access_token = if user.access_token.present?
user.access_token
else
user.create_access_token
end
end
# ----------------------------------------------------------------------------
private
def client
#client ||= Octokit::Client.new(
client_id: ENV['GITHUB_CLIENT_ID'],
client_secret: ENV['GITHUB_CLIENT_SECRET']
)
end
def token
#token ||= client.exchange_code_for_token(code)
end
def user_data
#user_data ||= Octokit::Client.new(
access_token: token
).user.to_h.slice(:login, :url, :avatar_url, :name)
end
def prepare_user
if User.exists?(login: user_data[:login])
#user = User.find_by(login: user_data[:login])
else
User.create(user_data.merge(provider: 'github'))
end
end
attr_reader :code
end
This is my RSpec file for User authentication:
require 'rails_helper'
RSpec.describe UserAuthenticator do
describe '#perform' do
let(:authenticator) { described_class.new('sample code') }
subject { authenticator.perform }
context 'then the code is invalid' do
let(:error) {
double("Sawyer::Resource", error:'bad_verification_code')
}
before do
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return(error)
end
it "should raise an error" do
expect{subject}.to raise_error(UserAuthenticator::AuthenticationError)
expect(authenticator.user).to be_nil
end
end
context 'when code is correct' do
let(:user_data) do
{
login: 'nklobuc1',
url: 'http://example.com',
avatar_url: 'http://example.com/avatar',
name: 'Nikola'
}
end
before do
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return('validaccestoken')
allow_any_instance_of(Octokit::Client).to receive(
:user).and_return(user_data)
end
it "should save the user when does not exist" do
expect{subject}.to change{ User.count }.by(1)
expect(User.last.name).to eq('Nikola')
end
it "should reuse already registered user" do
user = create :user, user_data
expect{subject}.not_to change{User.count}
expect(authenticator.user).to eq(user)
end
it "should create and set user's access token" do
pp subject
expect{subject}.to change{AccessToken.count}.by(1)
expect(authenticator.access_token).to be_present
end
end
end
end
This is the User model:
class User < ApplicationRecord
validates :login, presence: true, uniqueness: true
validates :provider, presence: true
has_one :access_token, dependent: :destroy
end
and finally, this is AccessToken model:
class AccessToken < ApplicationRecord
belongs_to :user, class_name: "User", :foreign_key => :user_id
validates_uniqueness_of :user_id
after_initialize :generate_token
private
def generate_token
loop do
break if token.present? && !AccessToken.exists?(token: token)
self.token = SecureRandom.hex(10)
end
end
end
I found an answer. The prepare_user returned nil when I tested
it "should save the user when does not exist" do
expect{subject}.to change{ User.count }.by(1)
expect(User.last.name).to eq('Nikola')
end
The prepare_user just needed a following tweak (another instance variable under else):
def prepare_user
if User.exists?(login: user_data[:login])
#user = User.find_by(login: user_data[:login])
else
#user = User.create(user_data.merge(provider: 'github'))
end
end

Rspec create action: expected #count to have changed by 1, but was changed by 0

I'm doing some controller testings on my app, and when I test the create action for creating a new service, it gives me this error: "expected #count to have changed by 1, but was changed by 0"
Theres my spec:
before do
#user = FactoryGirl.create(:user)
sign_in #user
end
describe "POST #create" do
context "with valid params" do
it "creates a new Service" do
expect {
post :create, params: { user_id: #user, service: FactoryGirl.attributes_for(:service) }
}.to change(Service, :count).by(1)
end
end
end
My factory:
FactoryGirl.define do
factory :service do
name "Service Test"
price 1
time 15
quantity 2
user
end
end
My model:
class Service
include Mongoid::Document
embedded_in :user
field :name, type: String
field :price, type: Float
field :time, type: Integer
field :quantity, type: Integer
validates :name, :price, :time, :quantity, presence: true
end
And the controller:
def create
#service = Service.new(service_params)
#service.user = current_user
if #service.save
redirect_to services_path
else
redirect_to "/services"
end
end
The error:
Failure/Error:
expect {
post :create, params: { user_id: #user, service: FactoryGirl.attributes_for(:service) }
}.to change(Service, :count).by(1)
expected #count to have changed by 1, but was changed by 0
I'm using mongoid, btw. Services is embedded in the User.

Rspec controller test failing post #create with association

I have a customer model that belongs to user, and my controller test for post#create succeeds. But I have a subscription model that belongs to both user and plan, and it is failing (I'm using rails 5.1.2).
Here's my spec:
#rspec/controllers/checkout/subscriptions_controller_spec.rb
require 'rails_helper'
RSpec.describe Checkout::SubscriptionsController, type: :controller do
describe 'POST #create' do
let!(:user) { FactoryGirl.create(:user) }
before do
sign_in user
end
context 'with valid attributes' do
it 'creates a new subscription' do
expect { post :create, params: { subscription: FactoryGirl.attributes_for(:subscription) } }.to change(Subscription, :count).by(1)
end
end
end
end
Subscription controller:
# app/controllers/checkout/subscriptions_controller.rb
module Checkout
class SubscriptionsController < Checkout::CheckoutController
before_action :set_subscription, only: %i[edit update destroy]
before_action :set_options
def create
#subscription = Subscription.new(subscription_params)
#subscription.user_id = current_user.id
if #subscription.valid?
respond_to do |format|
if #subscription.save
# some code, excluded for brevity
end
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: #subscription.errors, status: :unprocessable_entity }
end
end
end
private
def set_subscription
#subscription = Subscription.find(params[:id])
end
def set_options
#categories = Category.where(active: true)
#plans = Plan.where(active: true)
end
def subscription_params
params.require(:subscription).permit(:user_id, :plan_id, :first_name, :last_name, :address, :address_2, :city, :state, :postal_code, :email, :price)
end
end
end
Subscription model -
# app/models/subscription.rb
class Subscription < ApplicationRecord
belongs_to :user
belongs_to :plan
has_many :shipments
validates :first_name, :last_name, :address, :city, :state, :postal_code, :plan_id, presence: true
before_create :set_price
before_update :set_price
before_create :set_dates
before_update :set_dates
def set_dates
# some code, excluded for brevity
end
def set_price
# some code, excluded for brevity
end
end
I'm also using some FactoryGirl factories for my models.
# spec/factories/subscriptions.rb
FactoryGirl.define do
factory :subscription do
first_name Faker::Name.first_name
last_name Faker::Name.last_name
address Faker::Address.street_address
city Faker::Address.city
state Faker::Address.state_abbr
postal_code Faker::Address.zip
plan
user
end
end
# spec/factories/plans.rb
FactoryGirl.define do
factory :plan do
name 'Nine Month Plan'
description 'Nine Month Plan description'
price 225.00
active true
starts_on Date.new(2017, 9, 1)
expires_on Date.new(2018, 5, 15)
monthly_duration 9
prep_days_required 5
category
end
end
# spec/factories/user.rb
FactoryGirl.define do
factory :user do
name Faker::Name.name
email Faker::Internet.email
password 'Abcdef10'
end
end
When I look at the log, I notice that user and plan aren't being populated when running the spec and creating the subscription, which must be why it's failing, since plan is required. But I can't figure out how to fix this. Any ideas? Thanks in advance.
The issue is that, by your model definition, you can only create a Subscription that is associated to an existing Plan:
class Subscription < ApplicationRecord
belongs_to :plan
validates :plan_id, presence: true
end
You could have debugged this issue by either setting a breakpoint in the rspec test and inspecting the response.body; or similarly instead by setting a breakpoint in SubscriptionsController#create and inspecting #subscription.errors. Either way, you should see the error that plan_id is not present (so therefore the #subscription did not save).
The issue stems from the fact that FactoryGirl#attributes_for does not include associated model IDs. (This issue has actually been raised many times in the project, and discussed at length.)
You could just explicitly pass a plan_id in the request payload of your test, to make it pass:
it 'creates a new subscription' do
expect do
post(
:create,
params: {
subscription: FactoryGirl.attributes_for(:subscription).merge(post_id: 123)
}
end.to change(Subscription, :count).by(1)
end
However, this solution is somewhat arduous and error prone. A more generic alternative I would suggest is define the following spec helper method:
def build_attributes(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].include?(k)
end
end
This utilises the fact that build(:subscription).attributes does include foreign keys, as it references the associations.
You could then write the test as follows:
it 'creates a new subscription' do
expect do
post(
:create,
params: {
subscription: build_attributes(:subscription)
}
)
end.to change(Subscription, :count).by(1)
end
Note that this test is still slightly unrealistic, since the Post does not actually exist in the database! For now, this may be fine. But in the future, you may find that the SubscriptionController#create action actually needs to look up the associated Post as part of the logic.
In this case, you'd need to explicitly create the Post in your test:
let!(:post) { create :post }
let(:subscription) { build :subscription, post: post }
...And then send the subscription.attributes to the controller.

Rails Rspec error "undefined method" (railsturoial Chapter 10)

I'm going through Michael Hartl's priceless tutorial and I got stuck with some Rspec errors. I've double checked everything but it seems like I'm still missing something. Here's what the error messages look like.
The thing that's bothering me the most is when I was generating the Microposts model I accidentally made a typo in one of the options so I did rails destroy model Microposts to undo the generate command before generating the model again. I'm wondering if that has anything to do with the errors I'm seeing.
I really wish to finish this tutorial ASAP so I can get on with building my own web application. ANY help would be appreciated.
Here's what my code looks like.
micropost_pages_spec.rb
require 'spec_helper'
describe "MicropostPages" do
subject {page}
let(:user) {FactoryGirl.create(:user)}
before {sign_in user}
describe "micropost creation" do
before {visit root_path}
describe "with invalid information" do
it "should not create a micropost" do
expect {click_button "Post"}.not_to change(Micropost, :count)
end
describe "error messages" do
before {click_button "Post"}
it {should have_content('error')}
end
end
end
end
microposts_controller.rb
class MicropostsController < ApplicationController
before_filter :signed_in_user, only: [:create, :destroy]
def create
#micropost = current_user.micropost.build(params[:micropost])
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_path
else
render 'static_pages/home'
end
end
def destroy
end
end
static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
#micropost = current_user.microposts.build if signed_in?
end
def help
end
def about
end
def contact
end
end
user.rb (User model)
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
has_many :microposts, dependent: :destroy
before_save {self.email.downcase!}
before_save :create_remember_token
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, presence: true, length: {maximum: 50}
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX},
uniqueness: {case_sensitive: false}
validates :password, presence: true, length: {minimum: 6}
validates :password_confirmation, presence: true
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
The error is with this line:
#micropost = current_user.micropost.build(params[:micropost])
It should be:
#micropost = current_user.microposts.build(params[:micropost])
You're using micropost when you should be using microposts.

Rspec: Result matched using == but still gives "Failed" test result

Good day! I'm practising materials from "Ruby on Rails Tutorial" by Michael Hartle.
Below is the failure message I received, even though the "expected" and "got" seems to match. Would you please give me some suggestion to see how I should approach this issue?
Thank you so much!
Below is the implementation code:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :emp_id, :dept_id, :password, :password_confirmation
validates :emp_id, :presence => true
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(emp_id, submitted_password)
user = find_by_emp_id(emp_id)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
Below is the SPEC code:
require 'spec_helper'
describe User do
before(:each) do
#attr = {:name=>"Example", :dept_id=>01, :emp_id=>10, :password=>"pwdabcd", :password_confirmation => "pwdabcd" }
end
.
.
.
describe "password encryption" do
before(:each) do
#user = User.create!(#attr)
end
.
.
.
describe "authenticate method" do
it "should return the user on emp_id password match" do
matching_user = User.authenticate(#attr[:emp_id], #attr[:password])
matching_user.should == #user
end
end
end
end
Thank you so much for your kind assistance.
Have a nice day!
Kevin - when you see a failure message like that, the representation of the object (#<User ...>) is up to the object, so it's possible that it doesn't show you everything that is being compared by ==. My guess is it has something to do with :password_confirmation, but I'm not certain. It doesn't look like the implementation is using it yet, so try removing password_confirmation from #attr in the spec, and from the attr_accessible declaration, and see if it passes.

Resources