I have a simple model with sequence as a attribute as integer. When I am setting sequence as 1 it fail the simple test case ie:
context 'MathFactAttemptData' do
it 'should insert rows' do
math_fact_attempt_data = FactoryGirl.build :math_fact_attempt_data
#params[:request_params] = {
user_data: {
math_fact_attempt_data: [JSON.parse(math_fact_attempt_data.to_json)]
}
}
initial_math_fact_attempt_data_count = MathFactAttemptData.unscoped.count
post api_v3_user_data_path, #params
response.should be_success
response_body = JSON.parse response.body
response_body['user_data'].should be_nil
response_body['seed_data'].should be_nil
MathFactAttemptData.unscoped.count.should == initial_math_fact_attempt_data_count + 1
end
end
Factories:
factory :math_fact_attempt_data do
association :user, factory: :student
association :math_fact_attempt, factory: :math_fact_attempt
association :problem_type, factory: :problem_type
num1 1
num2 1
correct_answer 1
response 1
correct 1
#sequence 1
time_spent 1
choice_type "MyString"
selected_operator "MyString"
end
Uncommenting sequence fails the test case with issue ie:
API v3 POST /user_data.json Entities MathFactAttemptData should insert rows
Failure/Error: math_fact_attempt_data = FactoryGirl.build :math_fact_attempt_data
NoMethodError:
undefined method `to_sym' for 1:Fixnum
# ./spec/requests/api/v3/post_user_data_spec.rb:1116:in `block (5 levels) in <top (required)>'
Finished in 3.7 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/requests/api/v3/post_user_data_spec.rb:1115 # API v3 POST /user_data.json Entities MathFactAttemptData should insert rows
As Peter pointed out, sequence is a FactoryGirl method.
Try this for setting the sequence attribute:
FactoryGirl.define do
factory :math_fact_attempt_data do
add_attribute :sequence, 1
end
end
sequence is a FactoryGirl method which expects a symbol as it's first parameter. That's why you're getting the error you're getting.
Related
I have a model (payment) that belongs to another model (event), via a polymorphic association.
Some tests are failing because the owner model (event) is accessed by the payment model in validations, but the event is returning nil. All the features work fine when testing app directly in the browser.
I added some more comments to payment.rb below.
I've tried defining the association in the factories, but no luck.
What is the best way to set up this association in the spec?
# models/event.rb
class Event < ApplicationRecord
has_many :payments, as: :payable, dependent: :destroy
end
# models/payment.rb
class Payment < ApplicationRecord
belongs_to :payable, polymorphic: true
validate :amount_is_valid
def amount_is_valid
if amount.to_i > payable.balance.to_i
errors.add(:amount, "can't be higher than balance")
end
end
end
Both examples in this spec are failing.
# spec/models/payment_spec.rb
require 'rails_helper'
RSpec.describe Payment, type: :model do
let!(:event) { FactoryBot.create(:event, event_type: 'test', total: 10000, balance: 10000) }
let!(:user) {FactoryBot.create(:user)}
let!(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer_id: user.id,
payable_id: event.id,
status: 1,
)
}
describe 'Association' do
it do
# This will fail with or without this line
payment.payable = event
is_expected.to belong_to(:payable)
end
end
# Validation
describe 'Validation' do
describe '#amount_is_valid' do
it 'not charge more than event balance' do
# This will make the test pass. The actual spec has a lot more examples though,
# would rather just set the association once.
# payment.payable = event
payment.amount = 5000000
payment.validate
expect(payment.errors[:amount]).to include("can't be higher than balance")
end
end
end
end
Output
# bundle exec rspec spec/models/payment_spec.rb
Randomized with seed 42748
Payment
Association
should belong to payable required: true (FAILED - 1)
Validation
#amount_is_valid
not charge more than event balance (FAILED - 2)
Failures:
1) Payment Association should belong to payable required: true
Failure/Error: if amount.to_i > payable.balance.to_i
NoMethodError:
undefined method `balance' for nil:NilClass
# ./app/models/payment.rb:9:in `amount_is_valid'
# ./spec/models/payment_spec.rb:23:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:80:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:79:in `block (2 levels) in <top (required)>'
# ./spec/spec_helper.rb:108:in `block (2 levels) in <top (required)>'
2) Payment Validation #amount_is_valid not charge more than event balance
Failure/Error: if amount.to_i > payable.balance.to_i
NoMethodError:
undefined method `balance' for nil:NilClass
# ./app/models/payment.rb:9:in `amount_is_valid'
# ./spec/models/payment_spec.rb:39:in `block (4 levels) in <top (required)>'
# ./spec/rails_helper.rb:80:in `block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:79:in `block (2 levels) in <top (required)>'
# ./spec/spec_helper.rb:108:in `block (2 levels) in <top (required)>'
Top 2 slowest examples (0.29972 seconds, 71.6% of total time):
Payment Association should belong to payable required: true
0.28796 seconds ./spec/models/payment_spec.rb:18
Payment Validation #amount_is_valid not charge more than event balance
0.01176 seconds ./spec/models/payment_spec.rb:32
Finished in 0.4186 seconds (files took 4.31 seconds to load)
2 examples, 2 failures
Failed examples:
rspec ./spec/models/payment_spec.rb:18 # Payment Association should belong to payable required: true
rspec ./spec/models/payment_spec.rb:32 # Payment Validation #amount_is_valid not charge more than event balance
Update
Passing specs based on Schwern's feedback.
Still using a custom validation for amount, because balance is a field on the associated payable, not the payment (couldn't find a way to access an associated model from inside a built-in validation helper)
# payment.rb
class Payment < ApplicationRecord
belongs_to :payable, polymorphic: true
validates :payable, presence: true
validate :amount_is_valid
def amount_is_valid
if amount > payable.balance
errors.add(:amount, "can't be greater than balance")
end
end
end
# spec/models/payment_spec.rb
require 'rails_helper'
RSpec.describe Payment, type: :model do
let(:event) { FactoryBot.create(:event, event_type: 'test', total: 10000, balance: 10000) }
let(:user) {FactoryBot.create(:user)}
let(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer_id: user.id,
payable: event,
status: 1,
)
}
describe '#payable' do
it 'is an Event' do
expect(payment.payable).to be_a(Event)
end
end
describe '#amount' do
context 'amount is higher than balance' do
before {
payment.amount = payment.payable.balance + 1
}
it 'is invalid' do
payment.validate
expect(payment.errors[:amount]).to include("can't be greater than balance")
end
end
end
end
Your first test is not failing where you think it is. It's failing on the next line, is_expected.to belong_to(:payable).
You're setting payment, but you're testing the implicitly defined subject which will be Payment.new.
is_expected.to belong_to(:payable)
Is equivalent to...
expect(subject).to belong_to(:payable)
And since you have no defined subject this is...
expect(Payment.new).to belong_to(:payable)
Payment.new does not have payable defined and so the amount_is_valid validation errors.
To fix this, test payment directly. And I would suggest staying away from subject while you're learning RSpec. And you should not have to set payment.event, it's already set in the factory.
describe 'Association' do
expect(payment).to belong_to(:payable)
end
But I'm not aware of a belong_to matcher. You should not be directly checking implementation, but rather its behavior. The behavior you want is for payment.payable to return a Payable.
describe '#payable' do
it 'is a Payable' do
expect(payment.payable).to be_a(Payable)
end
end
The second failure is because you have incorrectly initialized your Payment. You're passing in payable_id: event.id but that does not set payable_type. Without payable_type it doesn't know what class the ID is for.
Instead, pass the objects in directly.
let!(:payment) {
FactoryBot.build(:payment,
amount: 300,
method: 'cash',
payer: user,
payable: event,
status: 1,
)
}
Some more general cleanups...
let! will always run the block whether it's used or not. Unless you specifically need that, use let and the blocks will run as needed.
You expect payable to exist, so validate the presence of payable.
Use the built in numericality validator on amount.
class Payment < ApplicationRecord
belongs_to :payable, polymorphic: true
validates :payable, presence: true
validates :amount, numericality: {
less_than_or_equal_to: balance,
message: "must be less than or equal to the balance of #{balance}"
}
end
require 'rails_helper'
RSpec.describe Payment, type: :model do
let(:event) {
create(:event, event_type: 'test', total: 10000, balance: 10000)
}
let(:user) { create(:user) }
let(:payment) {
build(:payment,
amount: 300,
method: 'cash',
payer: user,
payable: event,
status: 1
)
}
# It's useful to organize tests by method.
describe '#payable' do
it 'is a Payable' do
expect(payment.payable).to be_a(Payable)
end
end
describe '#amount' do
# Contexts also help organize and name your tests.
context 'when the amount is higher than the payable balance' do
# This code will run before each example.
before {
# Rather than hard coding numbers, make your tests relative.
# If event.balance changes the test will still work.
payment.amount = payment.payable.balance + 1
}
it 'is invalid' do
expect(payment.valid?).to be false
expect(payment.errors[:amount]).to include("must be less than or equal to")
end
end
end
end
I have a model that describes a patient (a Packet) which references the destination hospital and the transporting ambulance:
class Packet < ActiveRecord::Base
belongs_to :hospital
belongs_to :provider
validates_presence_of :hospital_id
validates_presence_of :provider_id
end
class Hospital < ActiveRecord::Base
has_many :packets
end
class Provider < ActiveRecord::Base
has_many :packets
end
and my RSpec specification:
require "rails_helper"
RSpec.describe Packet, :type => :model do
it "creates a new packet" do
hospital = Hospital.create(:name=>"Community Hospital")
provider = Provider.create(:name=>"Community Ambulance", :unit=>"Ambulance 3")
packet = Packet.new()
packet.hospital = hospital
packet.provider = provider
packet.save
end
end
RSpec fails with:
1) Packet creates a new packet
Failure/Error: packet.hospital = hospital
ActiveModel::MissingAttributeError:
can't write unknown attribute hospital_id
The thing I don't get is that the meat of my test (everything in the "it" block) runs fine in the rails console, with no errors. Why would I get the unknown attribute in the rspec test but not in the console?
Full stack trace:
Garys-MacBook-Air-2:EMSPacket gary$ rspec
F
Failures:
1) Packet creates a new packet
Failure/Error: packet.hospital = hospital
ActiveModel::MissingAttributeError:
can't write unknown attribute `hospital_id`
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/attribute.rb:124:in `with_value_from_database'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/attribute_set.rb:39:in `write_from_user'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/attribute_methods/write.rb:74:in `write_attribute_with_type_cast'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/attribute_methods/write.rb:56:in `write_attribute'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/attribute_methods/dirty.rb:92:in `write_attribute'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/attribute_methods.rb:373:in `[]='
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/associations/belongs_to_association.rb:80:in `replace_keys'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/associations/belongs_to_association.rb:14:in `replace'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/associations/singular_association.rb:17:in `writer'
# /Users/gary/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-4.2.0/lib/active_record/associations/builder/association.rb:123:in `hospital='
# ./spec/models/packet_spec.rb:9:in `block (2 levels) in <top (required)>'
Finished in 0.14495 seconds (files took 7.49 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/models/packet_spec.rb:4 # Packet creates a new packet
RSpec is trying to test everystep inside the it block, that's why the test is failing but the console works. You have to create the record with the attributes and relations before testing it, and then test something.
The code you pasted for the tests id not actually testing anything.
Try to tests things that can really fail, like saving with errors, or creating without associations. But not repeating the steps inside the test.
describe "When creating new package" do
let(:hospital) {Hospital.create(attributes)}
let(:provider) {Provider.create(attributes)}
let(:packet) {Packet.create(hospital_id: hospital.id, provider_id: provider.id)}
it "should have the associations linked" do
expect(package.hospital_id).to eq(hospital.id)
expect(package.provider_id).to eq(provider.id)
end
end
EDITED:
Remember to run your migrations for the test database:
rake db:test:prepare
I like to use https://github.com/thoughtbot/shoulda-matchers in my tests
it would be
describe 'associations' do
it { should belong_to :hospital }
it { should belong_to :provider }
end
describe 'validations' do
it { should validate_presence_of :hospital_id }
it { should validate_presence_of :provider_id }
end
Anyway take a look at https://github.com/thoughtbot/factory_girl, it will help you with tests also.
Credit to Jorge de los Santos, the problem was the setup of my test database. Thanks Jorge!
I am attempting to write a model test, like so:
require 'spec_helper'
describe Five9List do
before :each do
#five9_list = Five9List.new(name: 'test_list', size: '100')
end
describe "#new" do
it "takes two parameters and returns a Five9List object" do
#five9_list.should be_an_instance_of Five9List
end
end
describe "#name" do
it "returns the correct name" do
#five9_list.name.should eql "test_list"
end
end
describe "#size" do
it "returns the correct size" do
#five9_list.size.should eql 100
end
end
end
Currently, this succeeds and works fine. That's because my model is using attr_accessible, like so:
class Five9List < ActiveRecord::Base
attr_accessible :name, :size
end
If I want to get rid of attr_accessible and follow the rails 4 convention of using strong_params, how would I write that to where my rspec test would still succeed?
Adding this in my controller:
private
def five9_list_params
params.require(:five9_list).permit(:name, :size)
end
And removing attr_accessible does not work.
EDIT
Here is the error I receive from rspec .:
Failures:
1) Five9List#name returns the correct name
Failure/Error: #five9_list.name.should eql "test_list"
expected: "test_list"
got: nil
(compared using eql?)
# ./spec/models/five9_list_spec.rb:16:in `block (3 levels) in <top (required)>'
2) Five9List#size returns the correct size
Failure/Error: #five9_list.size.should eql 100
expected: 100
got: nil
(compared using eql?)
# ./spec/models/five9_list_spec.rb:22:in `block (3 levels) in <top (required)>'
Finished in 0.03303 seconds
4 examples, 2 failures, 1 pending
Failed examples:
rspec ./spec/models/five9_list_spec.rb:15 # Five9List#name returns the correct name
rspec ./spec/models/five9_list_spec.rb:21 # Five9List#size returns the correct size
Randomized with seed 20608
There's nothing wrong with your spec. I can only guess that you're not running Rails 4 or you've installed the ProtectedAttributes gem.
app/models/zombie.rb
class Zombie < ActiveRecord::Base
attr_accessible :name
validates :name, presence: true
end
spec/models/zombie_spec.rb
require 'spec_helper'
describe Zombie do
it "is invalid without a name" do
zombie = Zombie.new
zombie.should_not be_valid
end
end
errors
Zombie
is invalid without a name (FAILED - 1)
Failures:
1) Zombie is invalid without a name
Failure/Error: zombie.should_not be_valid
ActiveRecord::StatementInvalid:
Could not find table 'zombies'
# ./spec/models/zombie_spec.rb:5:in `new'
# ./spec/models/zombie_spec.rb:5:in `block (2 levels) in <top (required)>'
Finished in 0.02912 seconds
7 examples, 1 failure
Failed examples:
rspec ./spec/models/zombie_spec.rb:4 # Zombie is invalid without a name
Randomized with seed 12906
You should not be defining an initialize method in ActiveRecord classes.
When I have the initialize method defined, this error comes up:
Failure/Error: zombie = Zombie.new
NoMethodError:
undefined method `delete' for nil:NilClass
Without it, it passes as you expect it.
So, change your model to the following and your specs will pass.
class Zombie < ActiveRecord::Base
attr_accessible :name
validates :name, presence: true
end
OR... if you feel you HAVE to, make sure you call super first
def initialize(options={})
super
self.name = options[:name]
end
have you run rake db:test:load so the schema of the db is loaded on the testing db?
also your test is not right, you are testing that your model is invalid but you are not testing that your model is invalid BECAUSE it's name is not present, you should do something like
it "is invalid without a name" do
zombie = Zombie.new
zombie.should_not be_valid
zombie.errors[:name].should_not be_blank
end
that way you really know that the name attribute has an error
Thanks everyone, I was not running migrations so running rake db:migrate fixed it
I am also working through this tutorial and had the same problem. I raked the migration and still didn't get the desired results. As arieljuod mentioned, running $ rake db:test:load resolves the issue.
Here is the code
#add email of team lead of the current user
def add_the_tl_email(to_address, session_user_id)
ul = UserLevel.find_by_user_id(session_user_id)
if !ul.team.nil? && !ul.role.nil?
User.user_status('active').joins(:user_levels).where(:user_levels => {:position => 'tl',
:role => ul.role,
:team => ul.team }).each do
|u| to_address << u.email
end
end
end
The error from spec:
1) CustomersController GET customer page 'create' successful should create one customer record
Failure/Error: post 'create', :customer => #customer
NoMethodError:
undefined method `team' for nil:NilClass
# ./app/mailers/user_mailer.rb:36:in `add_the_tl_email'
# ./app/mailers/user_mailer.rb:15:in `notify_tl_dh_ch_ceo'
# ./app/controllers/customers_controller.rb:27:in `create'
# ./spec/controllers/customers_controller_spec.rb:48:in `block (5 levels) in <top (required)>'
# ./spec/controllers/customers_controller_spec.rb:47:in `block (4 levels) in <top (required)>'
team is a field in user_level. Here is the user_level in factory:
Factory.define :user_level do |level|
level.role "sales"
level.position "member"
level.team 1
level.association :user
end
In rails console, the ul.team.nil? returns either true or false on test data generated by factory.
Since team is a field in user_level, why there is error in spec like: NoMethodError:
undefined method `team' for nil:NilClass ?
Thanks.
The issues is that nil.team is not a method*
NoMethodError:
undefined method `team' for nil:NilClass
That is, ul evaluates to nil: check ul.nil? before checking ul.team.nil?... but might want to find out why the find failing to start with ;-)
Happy coding.
*This is what the real error message says, the title of the post is garbage :-/
The call in
ul = UserLevel.find_by_user_id(session_user_id)
returned nil and thus nil was assigned to ul.
So when you called
!ul.team.nil?
Ruby tried to call team on nil. nil is an instance of NilClass and there is no method named team on NilClass.