I have following Rspec test:
describe Productlimit do
before(:each) do
#productlimit = Factory.create(:productlimit, :user => Factory.create(:user))
end
subject { #productlimit }
...
it { should validate_uniqueness_of(:price_cents).scoped_to(:direction_down, :currency, :market_id, :user_id) }
...
end
But I get following confusing error:
1) Productlimit
Failure/Error: it { should validate_uniqueness_of(:price_cents).scoped_to(:direction_down, :currency, :market_id, :user_id) }
Expected errors to include "has already been taken" when price_cents is set to 9530, got errors: ["direction_down has already been taken (false)"]
Can you help me? I don't understand why this isn't working, because the error message seems to be correct?
EDIT:
This happens too in other situations as well:
# product_spec.rb
...
it { should validate_numericality_of(:price).with_message("price_cents must be greater than 0 (0)") }
# rake spec:models
Failure/Error: it { should validate_numericality_of(:price).with_message("price_cents must be greater than 0 (0)") }
Expected errors to include "price_cents must be greater than 0 (0)" when price is set to "abcd", got errors: ["price_cents must be greater than 0 (0)"]
To check validate_uniqueness_of(:field) but for this you should at lease one record in database to check uniquness constrain.
Here is....
before do
#country = FactoryGirl.create(:country, :id =>"a0000007-0000-0000-0000-000000000009",:currency_id => "a0000006-0000-0000-0000-000000000004",:merchant_id => "a0000001-0000-0000-0000-000000000002",:country_code => 'CAN', :name => 'Canada')
end
To validate uniqueness
it { should validate_uniqueness_of(:country_code)}
It will work out try it out.
To add to what Riche has said about uniqueness validation on :direction_down, I would suspect the way your ProductLimit factory generates values for :direction_down. It might not always be generating unique values for all attributes which have the uniqueness validation on them.
Also, one issue I have faced with uniqueness validation is the first object ( the subject in your case) thats is created before the validation check should not have any conflicting values with ones the factory "randomly" generates. To illustrate with a trivial example,
Factory(:product_limit, :direction_down => "abc")
it { should validate_uniqueness_of(:price_cents).scoped_to(:direction_down) }
has the potential to fail wrongly, in case the object constructed by the shoulda matcher ends with direction_down set to "abc" when there is a uniqueness validation on :direction_down.
Do you have the database_cleaner settings in the sepc_helper?
if not then add
gem "database_cleaner"
in spec_helper.rb
add the following in the RSpec.configure block
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
Also can you post the Factory code? just to have a clearer picture.
I hope this helps.
Also check if there is any uniqueness validation on :direction_down attribute.
Try to remove test/fixtures/* and try to manually create object before testing uniqueness (not using Factory). Also, have you tried:
class MyModelTest < ActiveSupport::TestCase
The error isn't correct for the matcher because it's complaining about the uniqueness of :direction_down when you asked it to test that the uniqueness of :price_cents was valid.
Does the code for your model include a validates_uniqueness_of :direction_down? If so, that would explain the message.
The first case (with validate_uniquess_of) happened to me after an unexpected crash. A simple rake db:test:prepare fixed it.
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
Using Rspec and FactoryGirl, if I have a factory that autoincrements a trait using a sequence, and in some specs if I explicitly set this trait, with a large enough test suite, sometimes random specs fail with
Validation failed: uniq_id has already been taken
The factory is defined like this:
factory :user { sequence(:uniq_id) {|n| n + 1000} }
I'm guessing this validation fails because in one place in my test suite, I generate a user like this:
create(:user, uniq_id: 5555)
And because presumably factory girl is generating more than 4,555 users over the suite, the validation is failing?
I'm attempting to avoid this problem by just turning the uniq_id into 55555 (larger number), so there is no interference. But is there a better solution? My spec_helper includes these relevant bits:
config.use_transactional_fixtures = true
config.after(:all) do
DatabaseCleaner.clean_with(:truncation)
end
It happens to me sometimes. I didn't found any explanation, but happens only with big set of data. I let someone find the explanation!
When it happens, you can declare your attribute like this (here is an example using faker gem) :
FactoryGirl.define do
factory :user do
login do
# first attempt
l = Faker::Internet.user_name
while User.exists?(:login => l) do
# Here is a loop forcing validation
l = Faker::Internet.user_name
end
l # return login
end
end
end
I was able to solve my issue like this in my factory (based on #gotva's suggestion in the question comments).
factory :user do
sequence(:uniq_id) { |n| n + 1000 }
# increment again if somehow invalid
after(:build) do |obj|
if !obj.valid? && obj.errors.keys.include?(:uniq_id)
obj.uniq_id +=1
end
end
end
This should be simple but I cannot figure out why it doesn't work. I have seen many more complex uniqueness constructs here. I have column that should be a unique index. I have specified it twice in the model, just testing options, but my test for uniqueness continues to fail.
Model validation code:
validates :feed_url, presence:true,
format: {with: VALID_URL_REGEX},
uniqueness: true
validates_uniqueness_of :feed_url
RSpec code:
before do
#feed = FactoryGirl.create(:feed)
end
...
describe "when URL is already taken" do
before do
feed_with_same_url = #feed.dup
feed_with_same_url.feed_url = #feed.feed_url
feed_with_same_url.save
end
it { should_not be_valid }
end
The save should not be valid but the tests fails because the model says it is valid even though the fields are equal. I have checked the fields myself at a breakpoint and they are exactly the same in case, length, value, etc.
Tests for presence and Regex validity work perfectly, so the model is working.
I am trying to do this in the model as opposed to the index. I believe I read last night that Rails 4 prefers (deprecates?) these tests in the code instead of the database, but I cannot find that source tonight. (?) Either way, I'd like to see the model working.
You have to call should_not be_valid on some object. Try this
before(:each) do
#feed_with_same_url = #feed.dup
#feed_with_same_url.feed_url = #feed.feed_url
end
it { #feed_with_same_url.should_not be_valid }
What is it in the context of that test? Without the full code, it's probably not feed_with_same_url, so your test is not checking what you think it's checking.
If I was writing this test, it would be something like:
let(:feed) { FactoryGirl.create :feed }
let(:feed_with_same_url) { feed.dup }
subject { feed_with_same_url }
it { should_not be_valid }
Now it is the subject, which is feed_with_same_url.
Here is my Lesson model:
before_create :set_sequence
def set_sequence
maxseq = Lesson.where(:course_id => self.course_id).maximum("sequence")
if (maxseq.nil?)
maxseq = 0
end
self.sequence = maxseq + 1
end
when I run rspec the following test fails:
it "validate sequence is setup" do
lesson = Lesson.create(:title => "Testing", :description => "Testing", :course_id => 1)
lesson.sequence.should_not eql nil
end
However when T test this through rails console the Lesson object is created successfully and with the correct sequence. Any ideas why?
lesson.sequence.should_not be_nil is the correct way to test for nil, as far as I know. Have you tried that?
Any validations you've got on Lesson could be silently aborting the create before your callback gets called. Change it to create! in the spec to check it.
FactoryGirl first initializes object with no parameters, and then assigns parameters one by one. The callback in your model probably would not work in this case. So you can try to change FactoryGirl's behavior by adding
initialize_with { new(attributes) }
to the Lesson's factory. I'm not sure it will help though. Testing callback behavior in Rails is tricky.
I'm using ruby 1.9.2 and rails 3.2.2.
I have a 'domain' model (domain.rb):
class Domain < ActiveRecord::Base
attr_accessible :url
belongs_to :user
VALID_DOMAIN_REGEX = /^[a-z0-9\-\.]+\.[a-z]{2,}$/i
validates :url, presence:true,
format: { with: VALID_DOMAIN_REGEX },
uniqueness: { case_sensitive: false }
end
And a test asserting that a duplicate domain should not be valid:
require 'spec_helper'
describe Domain do
before do
#domain = FactoryGirl.create(:domain)
end
subject { #domain }
describe "when domain url is already taken" do
before do
domain_with_same_url = #domain.dup
domain_with_same_url.url = #domain.url.upcase
domain_with_same_url.save
end
it { should_not be_valid }
end
end
The test keeps failing:
1) Domain when domain url is already taken
Failure/Error: it { should_not be_valid }
expected valid? to return false, got true
# ./spec/models/domain_spec.rb:31:in `block (3 levels) in '
#domain is already created, validated and saved.
The domain_with_same_url is the new record, and it should be invalid. But you are not checking it.
Try
domain_with_same_url = FactoryGirl.create(:domain, :url => #domain.url.upcase)
domain_with_same_url.should_not be_valid
Your two before blocks are run in outer-to inner order. Thus, when running your testsuite, first your #domain object gets created and saved, then the inner before block gets executed. Hiwever, your domain_with_same_url probably gets never actually saved because it's validation fails which probably leats to domain_with_same_url.save to return false.
As a workaround, you could check the validity of domain_with_same_url instead of #domain.
Seems like your subject of your test case is #domain, which is a valid object. Whether use new subject for #domain_with_same_url (don't forget to make it an instance variable), or explicitly say domain_with_same_url.should ... (just like The Who indicated in his answer).
I just had the same problem in Rails 4 Model column uniqueness not working. It provides two other solutions without a second call to FactoryGirl, just FYI. The one I am using is:
before(:each) do
#feed_with_same_url = #feed.dup
#feed_with_same_url.feed_url = #feed.feed_url
end
it { #feed_with_same_url.should_not be_valid }
The save is causing some problem, unexpected to me. And, you need to reference an object to should_not_be_valid as a local variable.