I'm trying to test PG database constraints in a rails 4 using RSpec, and I'm not sure how to set it up.
My thought was to do something like this:
before do
#subscriber = Marketing::Subscriber.new(email: "subscriber#example.com")
end
describe "when email address is already taken" do
before do
subscriber_with_same_email = #subscriber.dup
subscriber_with_same_email.email = #subscriber.email.upcase
subscriber_with_same_email.save
end
it "should raise db error when validation is skipped" do
expect(#subscriber.save!(validate: false)).to raise_error
end
end
When I run this, it does in generate an error:
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint
However, the test still fails.
Is there a proper syntax to get the test to pass?
Try
it "should raise db error when validation is skipped" do
expect { #subscriber.save!(validate: false) }.to raise_error
end
For more information, check the more info on rspec-expectations expect-error matchers
Hope this helps!
Slight modification to #strivedi183's answer:
it "should raise db error when validation is skipped" do
expect { #subscriber.save!(validate: false) }.to raise_error(ActiveRecord::RecordNotUnique)
end
The justification for being more verbose with the error class is that it protects you from having other possible checks that may raise an error that are not related to specific duplication error you wish to raise.
Related
I have a spec testing a model that looks like this:
RSpec.describe SomeModel, type: :model do
subject { described_class.new(test_amount: 99) }
describe 'validates a column' do
it 'does some validations' do
expect(subject).to validate_presence_of(:test_amount)
end
end
end
And a model that looks like this:
class SomeModel < ApplicationRecord
validates :test_amount, presence: true
end
And in the schema it's column looks like this with a not-null set:
t.integer "test_amount", default: 0, null: false
No matter what I do or where I put the code, test_amount is always nil when being tests and errors.
I've tried moving the test lines around, putting the subject in a before etc, but always the
database is throwing a non-null error and even if I raise in the model code
the test_amount value is not 99 it is nil. If I raise the test value in
a before like this:
before do
raise subject.test_amount
end
That does result in a 99, however if I remove this, it is always nil and throws an error when it gets to the expect part of the test.
What am I missing in order to get this test to work, I just cannot seem to get the test_amount to set to 99 when being tested in the actual test step.
The test always throws the error:
PG::NotNullViolation: ERROR: null value in column "test_amount" of relation "some_models" violates not-null constraint or similar, I have but in before-validations to check the value of test_amount and it does not get set.
Thanks for you help, I feel like there's something really basic I'm missing here.
First of all, verify that -
The record is persisted.
Once persisted, then check the attribute value.
Moreover, try moving this line inside the describe block -
subject { described_class.new(test_amount: 99) }
Any chance this is due to not running migrations in the test environment?
bundle exec rake db:prepare RAILS_ENV=test
# or for rack applications
bundle exec rake db:prepare RACK_ENV=test
Other than this I would think that because we aren't saving the record to the database. We wouldn't expect validations to be ran.
As per this documentation we are only expecting to run validations when Record#save or Record#save! has been called.
When running Record#new we are creating a new instance but not saving to our database.
Using the new method, an object can be instantiated without being saved:
When running Record#create we initialize the record and then save this to the database by calling Record#save.
You can write test cases for the presence validation with valid? and errors methods like below:
RSpec.describe SomeModel, type: :model do
subject { described_class.new(test_amount: test_amount) }
describe 'validates a column' do
context 'when valid test_amount'
let(:test_amount) { 99 }
it 'does not throw error' do
expect(subject.valid?).to eq(true)
expect(subject.errors[:test_amount].size).to eq(0)
end
end
context 'when invalid test_amount'
let(:test_amount) { nil }
it 'throws error' do
expect(subject.valid?).to eq(false)
expect(subject.errors[:test_amount].size).to eq(1)
end
end
end
end
I am new to RSpec. I have a method in my model user_profile.rb
def self.create_from_supplement(device, structure)
xml = Nokogiri.parse(structure.to_s)
user_profile = nil
auth_type = xml.%('auth_supplement/auth_type').inner_html
if 'user' == auth_type
user_details_str = xml.%('auth_supplement/supplement_data/content').inner_html rescue nil
return nil if user_details_str.blank?
user_details_xml = Nokogiri.parse(user_details_str)
user_name = user_details_xml.%('username').inner_html
user_profile = UserProfile.find_or_initialize_by(name: user_name)
if user_profile.save
device.update_attributes(user_profile_id: user_profile.id)
else
raise "User Profile Creation Failed because of #{user_profile.errors.full_messages}"
end
end
return user_profile
end
I am writing a unit test case to test when user_profile.save fails, the test case will expect an exception was raised. But in my user_profiles table I have only one column :name.
How to test the case when user_profile.save fails? The most important problem here is I dont find any way to make this user_profile.save to fail.
Some suggests using RSpec Stubs. How do we do that?
With Rspec expectations you have a special syntax for when you expect an error to be raised.
if you did something like this:
expect(raise NoMethodError).to raise_error(NoMethodError)
that wouldn't work - RSpec would not handle the error and would exit.
However if you use brackets:
expect { raise NoMethodError }.to raise_error(NoMethodError)
that should pass.
If you use brackets ( or a do / end block ) than any errors in the block will be 'captured' and you can check them with the raise_error matcher.
checkout rspec documents:
https://www.relishapp.com/rspec/rspec-expectations/v/2-11/docs/built-in-matchers/raise-error-matcher
describe ':: create_from_supplement' do
it 'blows up' do
expect { UserProfile.create_from_supplement(*args) }.to raise_error(/User Profile Creation Failed because of/)
end
end
Tracing back you code, here are the places that might cause the error, and following what you can consider.
user_details_str = xml.%('auth_supplement/supplement_data/content').inner_html
Here user_details_str might be an invalid string format (not nil) because whatever you get from 'auth_supplement/supplement_data/content' is not a correct format.
user_details_xml = Nokogiri.parse(user_details_str)
Here you need to determine what might cause Nokogiri::parse to give you an invalid result.
user_name = user_details_xml.%('username').inner_html
Then here, same as above.
user_profile = UserProfile.find_or_initialize_by(name: user_name)
So here, you might have an invalid user_name due to the previous few lines of code, which violates any validation you might have (e.g. too short, not capitalized, or what not).
More Info
So this can go deeper into your code. It's hard to test because your method is trying to do too much. And this clearly violates abc size (there are too many logical branches, more info here: http://wiki.c2.com/?AbcMetric)
I suggest refactoring some branches of this method into smaller single responsibility methods.
Rspec
context "has non ascii characters" do
it "will not call get_imdb" do
expect_any_instance_of(Celebrity).not_to receive(:get_imdb)
FactoryGirl.build(:imdb_celebrity, first_name: "Sæthy")
end
end
model:
def celebrity_status
if full_name.ascii_only?
get_imdb ## << -- returns string or nil
end
### If get_imdb returns nil (or isn't called), record is not valid
end
Validation fails if get_imdb returns nil or isn't called. My problem is that I'm trying to test the ascii characters portion of the method, but by doing that - my validation is failing and giving me a "Validation Failed" error in the console when running Rspec tests.
But that's what I want to happen... I want the validation to fail.
How can I solve this?
By itself, a model validation failure should not raise an exception. You may be attempting to save the record, which would case save to fail, and might raise an exception if called via save! or create!.
You can test whether the model is valid by calling valid?
expect(celebrity.valid?).to be false
It is also useful to check the error hash to see that it has the expected contents:
celebrity.valid? # => false
expect(celebrity.errors[:my_error]).to equal "my error message"
If you want to test your code on having exceptions, you should use Rspec's raise_errormatcher:
expect { 3 / 0 }.to raise_exception
More info here: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/raise-error-matcher
I'm using capybara to do some web automation.
There are various points in the code where it says things like
raise_error 'Failed as this is a duplicate' if duplicate?
or
raise_error 'Failed to log in' if logged_in? == false
All of this is abstracted to a module and I'd prefer that module to not rely on anything in the models.
What I'm struggling with is how to access that error text when I'm running it, from outside the model.
i.e.
Class Thing
has_many :notes
def do_something
#done = Module::Task.something(self.attribute)
if #done
self.update_attributes(status:'Done')
else
self.notes.new(text: error.text)
end
end
but I can't work out the syntax to get that error text.
Answer: If I understand you correctly then errors that appeared while completing the task
#done = Module::Task.something(self.attribute)
can be accessed via #done.errors.messages
Example: If I have User model where attribute username has 2 validations: presence and format then error messages display like this:
irb(main):019:0* u = User.new
irb(main):022:0* u.save # wont succeed
irb(main):028:0* u.errors.messages
=> {:uid=>["can't be blank", "is invalid"]}
If you want to test error messages with capybara then you can use the syntax like this:
it 'raises jibberishh' do
expect{User.raise_error_method}.to raise_error("jibberishh")
end
My application is propagating a ActiveRecord::RecordNotFound to the controller when a model can't retrieve a record.
Here is the DB query:
def self.select_intro_verse
offset = rand(IntroVerse.count)
IntroVerse.select('line_one', 'line_two', 'line_three', 'line_four', 'line_five').offset(offset).first!.as_json
end
first! raises a ActiveRecord::RecordNotFound if no record is found, and I am able to rescue it and render an appropriate template in my controller.
This is the expected behavior so I would like to have a test for it in my specs. I wrote:
context "verses missing in database" do
it "raises an exception" do
expect(VerseSelector.select_intro_verse).to raise_exception
end
end
When I run rspec:
1) VerseSelector verses missing in database raises an exception
Failure/Error: expect(VerseSelector.select_intro_verse).to raise_exception
ActiveRecord::RecordNotFound:
ActiveRecord::RecordNotFound
To me the test fails even though the exception is raised! How can I make my test pass?
Look in documentation for rspec-expectations#expecting-errors this:
expect(VerseSelector.select_intro_verse).to raise_exception
should be except syntax for exception must be lambda or proc:
expect { VerseSelector.select_intro_verse }.to raise_exception(ActiveRecord::RecordNotFound)