I am using Ruby on Rails 3.0.9 and RSpec 2. I would like to know what "validation logic" I should test. That is, if in my model I have:
class User < ActiveRecord::Base
validates :firstname
:presence => true
end
What should I test of the following?
"should have a valid first name"
"should not have a valid first name"
Or should I test both?
You can test both by simply doing this:
it "should validate presence of" do
should validate_presence_of :firstname
end
Take a look at the shoulda matchers for all such standard Rails Validation.
I think you should not test both. This will be enough:
describe User do
it { should validate_presence_of(:firstname) }
end
There is basically no algorithm for validating names, because the form of names is incredibly culture-centric. So, really you should avoid complex validations for something like a person's name. Some places/cultures don't have last names, for example, so even validating their presence isn't proper. There's a whole list of other examples that make validating names a really bad idea. For more information on the issue of validating names itself, see my answer to this question.
That being said, in general, when validating any field, I test both valid and invalid data. I make sure that, when I set a field to a valid value, that the .valid? method returns true, and when it's invalid, that it returns false.
Typically you don't need to do a long list, you just need to test
A typical valid and invalid example
A few edge cases
you can also test for specific values:
describe User do
context "validations" do
it { should_not allow_value("admin").for(:firstname) }
it { should allow_value("JohnDoe").for(:firstname) }
end
end
Related
Following Michael Hartl's Rails tutorial, I'm unsure how the following validation test is supposed to work:
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
When this test is written, the testing suite should be Red. After adding the corresponding part in the User class as such:
class User < ActiveRecord::Base
validates :name, presence: true
end
The test becomes Green. I don't understand how the former part works. Is the test Red because the validates part is not implemented yet? After implementing it, #user.valid? should be False turning into a True due to assert_not. Thus, the test is Green?
You have the correct understanding of it. Before a model in Rails is saved to the database, it must be validated. The test here is checking whether the user model would be considered valid and saved with a blank name. As such, before you add validates :name, presence: true, #user.valid? evaluates to true and the test fails. Once you add the validation, the model is considered invalid.
In a Test driven development (TDD) approach we write test before writing codes. Thus, we make the test fail first and then write some codes to make the test pass.
Looks like you do have a correct understanding of whats going on.
assert_not #user.valid? is saying that the #user object is invalid. To start with #user.name is set to a blank value; so we expect it the assertion to pass. However, the code is not yet checking for a valid name to be present. So it fails.
Adding validates :name, presence: true makes the model check for a valid name to be present. Thus the test passes.
I would like to test the uniquness of User model.
My User model class looks like:
class User
include Mongoid::Document
field :email, type: String
embeds_one :details
validates :email,
presence: true,
uniqueness: true,
format: {
with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z0-9]{2,})\Z/i,
on: :create
},
length: { in: 6..50 }
end
My rspec test which belongs to the model looks like:
...
before(:each) do
FactoryGirl.create(:user, email: taken_mail)
end
it "with an already used email" do
expect(FactoryGirl.create(:user, email: taken_mail)).to_not be_valid
end
After I executed bundle exec rspec it always raises the next error instead of passed with success:
Failure/Error: expect(FactoryGirl.create(:user, email: taken_mail)).to_not be_valid
Mongoid::Errors::Validations:
Problem:
Validation of User failed.
Summary:
The following errors were found: Email is already taken
Resolution:
Try persisting the document with valid data or remove the validations.
If I use this it passes with success:
it { should validate_uniqueness_of(:email) }
I would like to use expect(...). Can anybody help me out?
The issue is you are trying to persist an invalid object into the database, which throws an exception and breaks the test (because email is not unique), before even the test is done using the expect method.
The correct way is to use build here instead of create, which doesn't persist the object in the database, by building the record only in memory and allowing your test to do its job. Therefore to fix it:
expect(FactoryGirl.build(:user, email: taken_mail)).to_not be_valid
Also note that is better to use build rather than create if you don't need to actually save the record in the database, since it's a cheaper operation and you will get the same outcome, unless for some reason your record must be saved to the database for your tests to work in a way you want them, such as saving the first record in in your example.
i use ruby on rails. also i use minitest framework for testing and mongoid for database. i want to write a model test. my model is below:
class Identity
include Mongoid::Document
include OmniAuth::Identity::Models::Mongoid
field :name
field :email
field :password_digest
validates :name, uniqueness: true
end
the model test is:
describe Identity do
it "must include OmniAuth::Identity::Models::Mongoid" do
Identity.must_include OmniAuth::Identity::Models::Mongoid
end
it "should have name" do
Identity.new.must_respond_to :name
end
it "should have email" do
Identity.new.must_respond_to :email
end
it "should have password_digest" do
Identity.new.must_respond_to :password_digest
end
it "should type of String" do
Identity.new.name.type.must_equal "String"
end
end
my problem is about testing the field's type
it "should type of String" do
Identity.new.name.type.must_equal "String"
end
How can i test a field's type? Thanks in advance.
Just a tip - while not an answer to your actual question, it is usually not that
wise test implementation details of code, because it is more likely
to change, and if you think of system validation through tests, it is not that important
how it is implemented, but what it does, eg. how it behaves.
A canonical example is that of testing functionality of a Stack class. Instead of
pushing and popping items out of stack and checking the size, it is likely better to
just push and pop things, and see that if you pop an empty stack, you get appropriate
exception. And naturally you want to check that items are returned in last in, first out
(LIFO) order.
So, in your case, instead of testing what kind of type your field name is, rather
test what you do with the name.
I've been working through the tutorials at railstutorial.org, and I was a little stumped by the author's code for section -- 6.2.1 Validating presence.
In the user model, the tutorial adds validates :name, :presence => true. Simple enough.
When the author chooses to write the rspec test, he does something that I thought was a little strange.
describe User do
before(:each) do
#attr = { :name => "Example User", :email => "user#example.com" }
end
.
.
.
it "should require a name" do
no_name_user = User.new(#attr.merge(:name => ""))
no_name_user.should_not be_valid
end
end
Why go through the trouble to merge a blank string to #attr when one could get rid of the :each block statement and simply write:
it "should require a name" do
no_name_user = User.new(:name => "", :email => "user#example.com")
no_name_user.should_not be_valid
end
I know that the author uses the #attr variable to validate the presence of the email address as well, which is one indication as to why he used the block statement -- to me it makes more sense to follow the structure of the second block quote. Still, I have a feeling there is something that I'm missing here.
Another explanation that crossed my mind is that it helps to use the #attr structure when there are lots of keys to be entered, as opposed to this rather simplistic case of only name and email.
Anyone have any input?
It's so there's one standard attributes map that can be used across all the tests. When a test requires that a value isn't there, it's removed.
Personally, I wasn't convinced it was worth it as it sort of obfuscates things (as you discovered), but there it is.
The point is to only have code relevant for the test case in the test. The only relevant attribute when testing that the user isn't valid without a name is the name attribute. That test should not have to know anything about the email attribute.
Let's say you add validation for the presence of a new field – you'd have to update every test where you build up a User without that field. With the attr hash at the top, you just pop the new field in there, and all your tests are fine.
Creating objects for testing is a common enough problem that there are many solutions, and plenty of discussion about which way is best. I'd suggest you look into factories. Machinist and FactoryGirl are two alternatives that work great with Rails.
Everybody knows that automated testing is a good thing.
Not everybody knows exacly what to test.
My question is if native validations like validate_presence_of, validate_uniqueness_of and so on should be tested in the application.
In my office we are three, one thinks it should be tested, one thinks it shouldn´t and I am up in the air.
Yes.
Testing that a model attribute is present or not is only testing the validates_presence_of code as a by-product of the real test which is that the validates_presence_of exists within your model.
If someone commented out a bunch of validation code and then forgot to uncomment it then this would go undetected and could cause all sorts of problems.
I test them, not because I think they don't work but to ensure that they are present in my model when required.
Are you planning to write unit tests for every single Ruby operator and API method as well?
Your unit tests should test your own code, not other people's - that's their job, why duplicate their work? And if you don't trust them to do their job well, why are you using their code?
Matthew Bass has a great gem he's released for just this type of thing. It adds rspec matchers that check to make sure the validation is in place without actually running the underlying ActiveRecord code. Read more about it here.
It adds matchers for validations:
it_should_validate_presence_of :first_name, :last_name, :email
it_should_validate_numericality_of :zip
it_should_validate_uniqueness_of :email
it_should_validate_inclusion_of :gender, :in => %w(Male Female)
Also matchers for associations:
it_should_belong_to :employer
it_should_have_many :friends, :romans, :countrymen
it_should_have_one :account
it_should_have_and_belong_to_many :comments
And a few other useful additions:
# tests that User.count increases by 1
it_should_be_createable :with => {:first_name => 'Ed', :last_name => 'The Duck', :email => 'a#b.com'}
# tests that the attribute is protected
it_should_protect :email
That's not by any means an exhaustive list. I've got a fork where I've added a few others I needed, likely there are others floating around as well. It's a good approach and for me fit the middle ground between ensuring the validations were still in the model, and having to explicitly write tests to execute ActiveRecord code to ensure it.
This is where tools like Shoulda really come in handy. I think its totally up to you to test how you write the code with the tools people provide you. Just because you are using has_many, does not mean you are using it right!
Seriously, if you integrate Shoulda into the mix, it becomes trivial to test these sorts of things. if you run rcov, its going to tell you all the code you wrote is not fully tested, unless you do.
Test the code you write. ActiveRecord has great test coverage including coverage for the validation class methods.
Spec:
require 'spec_helper'
describe User do
before(:each) do
#user = User.new
end
it "should not be valid without an email" do
#user.save.should be_false
#user.should_not be_valid
#user.email = "example#example.com"
#user.should be_valid
#user.save.should be_true
end
end
To get that spec to pass you would need
class User < ActiveRecord::Base
validates_presence_of :email
end