Reputation: 15838
I have a User model with this validation
validates :name, :lastname, :format => {:with => /^[a-zA-Z]+$/, :message => 'Only letters and spaces allowed.'}
I'm not sure how to properly test it.
I've done a function that returns a random string made by 10 chars from an array of a-zA-z chars.
def get_random_name
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split('').shuffle[0..10].join
end end
and then I get a new name for each run of my specs.
I don't want to test that it uses some regexp, because then I would be testing the implementation and not the behavior, and I also don't want to test only one hardcoded case.
My questions are: Should I do that? Is it really needed? Is it better or useless? Do you know any better way to test that kind of validations?
EDIT: another question, what about generating invalid random names? is there a way to create random names that includes at least one char outside the allowed values? I can't hardcode an array of all invalid values to randomize it since it would be TOO big
Upvotes: 8
Views: 7200
Reputation: 9428
Testing against random data is a common testing technique called Fuzzing. I would look at using FuzzBert's Generators to create true random binary data to test against.
Here is an example of some great random data that you would not want getting past your validations.
irb> require 'fuzzbert'
=> true
irb> test_string = FuzzBert::Generators.random_fixlen(10)[]
=> "S\x1EU1\x11HVY\x0E\xD0"
irb> puts test_string
S▲U1◄HVY♫�
=> nil
Random Is Good
Because it is converting random binary bits to strings, you are going to get some really funky results to test against. That is a good thing! Not only will it test against using known symbols like ?, but also all kinds of combinations of valid and invalid characters.
Random is sometimes valid
There is a chance you will get valid data every now and then, although very unlikely. Also, the odds of getting valid data decrease the longer the random string you create is.
A first attempt to solve this problem could be to just append an invalid character to every output, but I wouldn't suggest it. For example, if you always append "!", then it would make your test equivalent to making sure the string doesn't have a "!" in it and not a true test of the full regex possibilities.
I would suggest testing your random strings against that same regex, if it does pass it, generate a different string to test with.
Finally, you could just ignore the rare chance that the random data is valid. If every once-in-a-while this particular rspec fails, look at what string it failed on, if it was a valid string, then just rerun.
Test all the possibilities
You will never be able to test all non-valid strings (unless you have a short maximum length), but by using Fuzz Testing you will be able to test against strings that contain a large variety of valid and invalid characters.
Upvotes: 1
Reputation: 864
Checkout the open-source project Shoulda Matchers: https://github.com/thoughtbot/shoulda-matchers
Edit: Sorry just noticed that Paul Fioravanti
mentioned Shoulda also. However you don't need to use FactoryGirl to create instances of the model. Using create
isn't necessary for a validation tests.
You can create unit tests directly on the model:
describe User, 'Validations' do
it { should allow_value("Name").for(:name) }
it { should_not allow_value("Inv4lid_").for(:name) }
end
Upvotes: 9
Reputation: 16793
In order to test valid and invalid formats (I think a regex is fine to define a format to validate against), how about defining what you think valid and invalid names are in some helper utility methods that you could perhaps refine over time if necessary. For example:
spec/support/utilities.rb
def valid_names
%w[Homer bart LISA]
end
def invalid_names
%w[BuRn$ 5M1+h3Rs♡]
end
Then, you could write tests for :name
(and :lastname
) using RSpec, shoulda-matchers, and Factory Girl that look something like this:
spec/models/user_spec.rb
describe User do
let(:user) { FactoryGirl.create(:user) }
# ...
describe "validations" do
context "for name, lastname" do
context "when format is invalid" do
invalid_names.each do |invalid_name|
it { should_not allow_value(invalid_name).for(:name) }
it { should_not allow_value(invalid_name).for(:lastname) }
end
end
context "when format is valid" do
valid_names.each do |valid_name|
it { should allow_value(valid_name).for(:name) }
it { should allow_value(valid_name).for(:lastname) }
end
end
# ...
end
# ...
end
# ...
end
If you ever intend to internationalize your app in the future, just remember that not all the world's names conform to this format.
Upvotes: 5