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!
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 can't figure out how to give a file in one of my Factories. I'm using the default test suite of rails 5, Factory Bot, and Paperclip. This is the test I'm running:
require 'test_helper'
class AttachmentTest < ActiveSupport::TestCase
test "should be valid" do
p "should look into: #{ActionController::TestCase.fixture_path}"
ressource = build(:attachment)
assert ressource.valid?
end
end
This is my fixture:
include ActionDispatch::TestProcess
FactoryBot.define do
factory :attachment do
file { fixture_file_upload('files/invoice.pdf', 'application/pdf') }
association :attachable, factory: :mission
end
end
I've invoice.pdf in /Users/Daniel/GitHub/haeapua-rails/test/fixtures/files (both values copy / pasted here directly from terminal)
The trace I get
# Running:
"should look into: /Users/Daniel/GitHub/haeapua-rails/test/fixtures/"
E
Error:
AttachmentTest#test_should_be_valid:
RuntimeError: files/invoice.pdf file does not exist
test/factories/attachments.rb:5:in `block (3 levels) in <top (required)>'
test/models/attachment_test.rb:7:in `block in <class:AttachmentTest>'
bin/rails test test/models/attachment_test.rb:5
I'm missing something but can't figure out what. As I'm using FactoryBot, does it change the fixtures/files path? How could I know it?
Edit
Try to move the invoice to this places:
/Users/Daniel/GitHub/haeapua-rails/test/fixtures/files/invoice.pdf
/Users/Daniel/GitHub/haeapua-rails/test/factories/files/invoice.pdf
/Users/Daniel/GitHub/haeapua-rails/test/files/invoice.pdf
/Users/Daniel/GitHub/haeapua-rails/files/invoice.pdf
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.
My spec/models code as require 'spec_helper'
describe Student do
it "should be work" do
student = Student.find 1
puts student.version
end
end
When running the code it shows the following error..,
Failures:
1) Student should be work
Failure/Error: student = Student.find 2
ActiveRecord::StatementInvalid:
Could not find table 'students'
# ./spec/models/student_spec.rb:6:in `block (2 levels) in <top (require
Finished in 0.00109 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/models/student_spec.rb:4 # Student should be work
I'm having students table.Also, I'm using paper_trail gem.
After running rake db:test:prepare then it shows an error as.,
Failures:
1) Student should be work
Failure/Error: s = Student.find 1
ActiveRecord::RecordNotFound:
Couldn't find Student with id=1
# ./models/student_spec.rb:5:in `block (2 levels) in <top (required)>'
Finished in 0.02182 seconds
1 example, 1 failure
Failed examples:
rspec ./models/student_spec.rb:4 # Student should be work
It seems there is no table students in test environment, try to run
$ bundle exec rake db:test:prepare
Do you have propagated some data in your test database (using fixtures or something like FactoryGirl gem)?
Otherwise you wouldn't find any "students" in your DB.
Test-DB and Development DB have nothing in common. In fact the Test-DB will be cleared for each test.
The problem is there is no studend with id=1 in the students table of the test DB (the test DB is cleared before start new tests).
What do you want to test?
Maybe you want to use the before to insert a student at the start of the spec:
describe Student do
before do
#student = Student.new(:version => 15)
#student.save
end
it "should be work" do
student = Student.first
# Test something ...
end
end
The before block is run before every test, in this case you can get the first student in the (test) DB because you inserted it in the before block (but I don't know what you're trying to test and if save a student is really needed).