I am piggybacking on my earlier question where I have a factory creating a parent and child. I am running my unit tests and have a simple one not passing.
test "invoice can save" do
invoice = build(:invoice)
assert invoice.save, "Error message: #{invoice.errors.full_messages}"
end
Returns the following error.
Error message: ["Trips can't be blank"]
But the following code works.
test "invoice can save" do
invoice = create(:invoice)
assert invoice.save, "Error message: #{invoice.errors.full_messages}"
end
My understanding is that build should be holding it in memory until save is called. Isn't the first a better test to see if it is successfully saving to the database?
Based on the Factory in your earlier question, you are calling before_create to set the trip. However, you are not creating the object, you're building it.
If you changed that before_create to an after_build it should resolve that persnickety test!
Here's a helpful link to an old article from Thoughtbots about the "callbacks" available in FactoryGirl
Related
I am trying to build an RSpec test spec for my model: Logo that will ensure that only a singular record can be saved to the database. When I utilize the .build method for the second call to build a Logo, my test fails because FactoryBot is able to build out a Logo.
However, if I use the .create method for the second Logo entry in FactoryBot I receive an error for the test because my model raises an error, as instructed, based upon my model's method for the :only_one_row method.
How can I make this work using RSpec and FactoryBot?
Here is the code I have tried, unsuccessfully:
# app/models/logo.rb
class Logo < ApplicationRecord
before_create :only_one_row
private
def only_one_row
raise "You can only have one logo file for this website application" if Logo.count > 0
end
end
# spec/factories/logos.rb
FactoryBot.define do
factory :logo do
image { File.open(File.join(Rails.root, 'spec', 'fixtures', 'example_image.jpg')) }
end
end
# spec/logo_spec.rb
require 'rails_helper'
RSpec.describe Logo, type: :model do
it 'can be created' do
example_logo = FactoryBot.create(:logo)
expect(example_logo).to be_valid
end
it 'can not have more than one record' do
# Ensure there are no logo records in the database before this test is run.
Logo.destroy_all
example_logo_one = FactoryBot.create(:logo)
# This is where the trouble lies...
# If I go with .create method I error with the raised error defined in my model file...
example_logo_two = FactoryBot.create(:logo)
# ... if I go with the .build method I receive an error as the .build method succeeds
# example_logo_two = FactoryBot.build(:logo)
expect(example_logo_two).to_not be_valid
end
end
Your validation here is implemented as a hook, not a validation, which is why the be_valid call will never fail. I want to note, there's no real issue here from a logical perspective -- a hard exception as a sanity check seems acceptable in this situation, since it shouldn't be something the app is trying to do. You could even re-write your test to test for it explicitly:
it 'can not have more than one record' do
# Ensure there are no logo records in the database before this test is run.
Logo.destroy_all
example_logo_one = FactoryBot.create(:logo)
expect { FactoryBot.create(:logo) }.to raise_error(RuntimeError)
end
But, if there's a possibility the app might try it and you want a better user experience, you can build this as a validation. The tricky part there is that the validation looks different for an unsaved Logo (we need to make sure there are no other saved Logos, period) versus an existing one (we just need to validate that we're the only one). We can make it one single check just by making sure that there are no Logos out there that aren't this one:
class Logo < ApplicationRecord
validate do |logo|
if Logo.first && Logo.first != logo
logo.errors.add(:base, "You can only have one logo file for this website application")
end
end
end
This validation will allow the first logo to save, but should immediately know that the second logo is invalid, passing your original spec.
When I utilize the .build method for the second call to build a Logo, my test fails because FactoryBot is able to build out a Logo.
That is correct, build does not save the object.
However, if I use the .create method for the second Logo entry in FactoryBot I receive an error for the test because my model raises an error, as instructed, based upon my model's method for the :only_one_row method.
Catch the exception with an expect block and the raise_error matcher.
context 'with one Logo already saved' do
let!(:logo) { create(:logo) }
it 'will not allow another' do
expect {
create(:logo)
}.to raise_error("You can only have one logo file for this website application")
end
end
Note this must hard code the exception message into the test. If the message changes, the test will fail. You could test for RuntimeError, but any RuntimeError would pass the test.
To avoid this, create a subclass of RuntimeError, raise that, and test for that specific exception.
class Logo < ApplicationRecord
...
def only_one_row
raise OnlyOneError if Logo.count > 0
end
class OnlyOneError < RuntimeError
MESSAGE = "You can only have one logo file for this website application".freeze
def initialize(msg = MESSAGE)
super
end
end
end
Then you can test for that exception.
expect {
create(:logo)
}.to raise_error(Logo::OnlyOneError)
Note that Logo.destroy_all should be unnecessary if you have your tests and test database set up correct. Each test example should start with a clean, empty database.
Two things here:
If your whole application only ever allows a single logo at all (and not, say, a single logo per company, per user or whatever), then I don't think there's a reason to put it in the database. Instead, simply put it in the filesystem and be done with it.
If there is a good reason to have it in the database despite my previous comment and you really want to make sure that there's only ever one logo, I would very much recommend to set this constraint on a database level. The two ways that come to mind is to revoke INSERT privileges for the relevant table or to define a trigger that prevents INSERT queries if the table already has a record.
This approach is critical because it's easily forgotten that 1) validations can be purposefully or accidentally circumvented (save(validate: false), update_column etc.) and 2) the database can be accessed by clients other than your app (such as another app, the database's own console tool etc.). If you want to ensure data integrity, you have to do such elemental things on a database level.
I'm learning Rspec for Rails and looking for a way to ensure that simply creating a class instance without a name provided will not succeed. How can I make this test below pass by making changes to the Dog class code?
class Dog < ActiveRecord::Base
validates_presence_of :dog_name
end
describe Dog do
it "requires a dog name to be created" do
dog = Dog.new(dog_name: nil)
expect(dog.save).to be_false
end
end
It would also be helpful to know how to write another test to verify that when a dog_name is set that the record created successfully.
I'd highly recommend checking out the shoulda-matchers gem. This would allow you to do these tests extremely easily. With shoulda-matchers, your tests would be:
describe Dog do
context 'validations' do
it { should validate_presence_of(:dog_name) }
...
end
end
There is nothing wrong with your test. It should pass if your system is set up correctly. You'll need to share the error you're getting if you want help in understanding why it's currently failing.
As for testing for successful creation, you would need to provide more information about what you've tried or what reference you are using that you don't understand in order to meet the SO question quality requirements.
describe Dog do
it "requires a dog name to be created" do
dog = Dog.create(dog_name: nil)
expect(dog).to have(1).error_on(:dog_name)
end
end
For example:
I run a test and all asserts pass, but I run the test again, and in my case, I receive the following error:
Validation failed: Email has already been taken
It seems adding: sequence(:email) {|n| "nobody#{n}#xyz.com" } for factorygirl is pointless
The tests seem to pass sometimes and others fail for errors reasons like these.
Please advise on the problem/solution.
try deleting all the records from tables before running test case.
eg:-
describe User do
before(:each) do
User.delete_all
end
it "validate e-mail" do
(do staff..)
end
end
I´m not sure it is a definitive solution, but i added random numbers to my product references on the factories with factorygirl using the lazy attributes.
Example:
FactoryGirl.define do
factory :product do
reference {"AB"+rand(999).to_s}
description "Test Product"
quantity {(1..9999).to_a.sample}
price_per_unit {((1..999999).to_a.sample)/100.to_f}
end
end
Do you have any "before(:all)" block? maybe you are missing the corresponding "after(:all)" to clean the data. before(:each) acts as a transaction so the database gets cleaned, before(:all) works like a normal query, yout have to handle the cleanup in an after(:all) block.
I have a still pretty simple Rails application that I want to develop using BDD with Cucumber and TDD with RSpec. Currently, I am hanging at a test where I want to check that if a new instance of an Organizer (that's the model I have) cannot be created due to a validation error. I would like to check that the errors Array of the object to be created is not empty so that I can be sure that error messages are available for showing them in the view.
require 'spec_helper'
describe OrganizersController do
render_views
describe "POST 'create'" do
describe "with invalid arguments" do
before(:each) do
request.env["HTTP_REFERER"] = organizers_new_path
#organizer_args = { :name => "" }
end
it "should return a non-empty list of errors" do
post 'create', :organizer => #organizer_args
#organizer.errors.empty?.should_not be_true
end
end
end
end
I am developing based on Rails 3.2.9 with RSpec 2 and cucumber-rails.
Any suggestions are appreciated. Thanks!
You should use assigns method to get instance variable from controller action:
assigns(:organizer).errors.empty?.should_not be_true
The latest preferred syntax is:
expect(assigns(:organizer).errors.empty?).to_not be_true
thanks for the answer guys but I'd like to suggest a slightly nicer syntax:
expect(assigns(:organizer).errors).to_not be_empty
(unrelated to the question 👇)
Basically whenever you have a method that ends with ? you'll have the corresponding rspec matcher that starts with be_ e.g.
1.odd? #=> true
expect(1).to be_odd
I'm using FactoryGirl for my fixtures and am finding that it's not really producing useful validation errors.
I always get the message for activerecord.errors.models.messages.record_invalid.
Not sure what further details are needed to help diagnose this. This makes it an excruciatingly slow process to track each error down.
Example factory:
Factory.define :partner do |partner|
partner.sequence(:username){ |n| "amcconnon#{n}" }
partner.first_name "Bobby Joe"
partner.last_name "Smiley"
partner.sequence(:email){ |n| "bob{n}#partners.com" }
partner.phone_number "5557 5554"
partner.country_id 75
partner.password "password"
partner.password_confirmation "password"
end
Then Factory(:partner) => "ActiveRecord::RecordInvalid Exception: Looks like something went wrong with these changes"
The actual problem is of course the email sequence doesn't use n properly and there is a unique validation on email. But that's for illustrative purposes.
rails => 3.2.2
factory_girl 2.6.1
Any other deets needed to help diagnose this?
(Note: edited this just to add an easier to read factory)
EDIT:
As per bijan's comment: "What exactly am I trying to do."
Trying to run "rspec spec". I would like when I use a factory like Factory(:partner) in this case for the error message when that fails to contain the same error I would get from Partner.new({blah...}).valid? then looked at the validation failures.
I'm not sure if this is exactly the same problem you were having, but it's a problem I was having with validation error messages not being very useful and I thought it could be useful to others searching for this problem. Below is what I came up with. This could be modified to give different info too.
include FactoryGirl::Syntax::Methods
# Right after you include Factory Girl's syntax methods, do this:
def create_with_info(*args, &block)
create_without_info(*args, &block)
rescue => e
raise unless e.is_a? ActiveRecord::RecordInvalid
raise $!, "#{e.message} (Class #{e.record.class.name})", $!.backtrace
end
alias_method_chain :create, :info
Then, when you use create :model_name, it will always include the model name in the error message. We had some rather deep dependencies (yes, another problem), and had a validation error like, "name is invalid"... and name was an attribute on a few different models. With this method added, it saves significant debugging time.
I think the key point here is that when you're setting up a test you need to make sure that the code you're testing fails at the point that you set the expectation (i.e. when you say "should" in Rspec). The specific problem with testing validations is of course that the validation fails as soon as you try to save the ActiveRecord object; thus the test setup mustn't invoke save.
Specific suggesions:
1) IMO Factory definitions should contain the minimum information required to create a valid object and should change as little as possible. When you want to test validations you can override the specific attribute being tested when you instantiate the new test object.
2) When testing validations, use Factory.build. Factory.build creates the ActiveRecord instance with the specified attributes but doesn't try to save it; this means you get to hold off triggering the validation until you set the expectation in the test.
How about something like this?
it "should fail to validate a crap password" do
partner_with_crap_password = Factory.build(:partner, :password => "crap password")
partner_with_crap_password.should_not be_valid
end