On the last post we’ve created a User Sign Up page and in this post we’ll continue to add some validation to the User to make sure that User is valid so it is safe to save or update User to the Database.
Validation
There are few rules that we’ve mention in last post about some validation logic:
- Email - Required, Unique, valid email format and after creation it cannot be updated anymore or for short read-only.
- Password - Required and at least 8 characters long.
- Full Name - Required.
Validation for Required Attributes
We had one pending test up till now in spec/models/user_spec.rb
. We will use this test
file to set up the Validation for User in TDD way.
First remove pending part generated on User Model:
pending "add some examples to (or delete) #{__FILE__}"
User Factory
First lets create a Factory of User that we’ll use in tests. This will be a valid Model of User:
1 2 3 4 5 6 7 8 |
|
RSpec let and subject and Factory Girl build
Then we’ll learn some of RSpec and Factory Girl, but first add this beneath the describe
block:
1 2 3 4 5 6 7 |
|
RSpec let
description borrowed from the RSpec documentation:
Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method’s invocation before each example.
RSpec subject
Use subject in the group scope to explicitly define the value that is returned by the subject method in the example scope.
There is also used a Factory Girl build
that returns a User instance that’s not saved, use create
if it is mandatory that Model is saved to database before getting it in tests.
Required Fields Model Tests
The email
, password
and full_name
are required so we create the RSpec context
named
is invalid and even though we should go one by one test for each attribute, for quickness we’ll
do them at once:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Note:
The
#
is used to denote the Ruby way of describing the instance methods, the.
is used for the class methods!
The should_not
can be used since we set a subject
to be instance of User built from
Factory Girl :user
factory so the RSpec knows to what the should_not
refers to.
The be_valid
method is a RSpec shorthand for the Rails valid?
method that returns
boolean hence the ?
, every Ruby method with ?
can be called in RSpec with be_<name_of_method>
.
The running tests should failing with message:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
To make the test green, add the validates presence
for all three required properties
in the app/models/user.rb
:
1 2 3 4 5 6 7 8 9 |
|
The tests should all pass:
Finished in 0.81772 seconds
5 examples, 0 failures
Validation of Email Uniqueness
Another validation for email
is that is need to be unique or there should not be two same
email
s stored in the database.
Add the new it
test to spec/models/user_spec.rb
in is invalid context
:
1 2 3 4 5 6 7 8 |
|
This test is little sketchy, firstly because there are two assertions and secondly of
saving our subject
User, then build
identical User, store him to user1
variable, and
then try to save User to database.
The second assertion is just to make sure that error is raised because of the email
uniqueness.
The failing message:
1 2 3 |
|
So the only thing is for us to prevent having email
stored to database more than once
with uniqueness
added to existing email vaildates
:
1
|
|
This should make the test green:
Finished in 0.8653 seconds
6 examples, 0 failures
Validation of Email format
Next there is need to make sure that the email
format is valid. The Regular Expression
is used to validate the email
format.
Note: There are better ways to do the complex Mail validation in Ruby or Rails, but it is out of scope of this simple app!
Add test below latest one, still in the is invalid context
:
1 2 3 4 |
|
The test should fail with message:
1 2 3 |
|
To fix it simple as possible add the format
to email validates
:
1 2 3 |
|
This should make to pass the test:
1 2 |
|
Validation of at least 8 chars for Password
The User entered password
must be at least 8 characters.
The password
will also be simple as possible without checking that there are at least
one number or symbol, but rather, just to have at least 8 characters!
Add new test as last in context
is invalid:
1 2 3 4 |
|
The test should fail with message:
1 2 3 |
|
To make test pass add the length
to the password validates
:
1
|
|
The test should pass now:
Finished in 1.09 seconds
8 examples, 0 failures
The Email must be read-only
The email
can only be set when is created and after saving to the database that email
must not ever be possible to change.
Outside of the context
is invalid create the new it
test:
1 2 3 4 5 |
|
This test is a little bit weird, what it ensures that when attributes are updated and
reloaded from the database, that the email
is still same as when it was created even
though is changed to new value.
The test should fail with message:
1 2 3 |
|
To make sure that email
is never changed after creation and all attempts to change the
email
will be silently ignored, use Rails attr_readonly
for email
.
Then the app/models/user.rb
should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
And the test should pass:
Finished in 1.28 seconds
9 examples, 0 failures
Test that User is valid
We tested all invalid combinations of the User to make sure that the logic we wanted is implemented, now for sanity check we’ll add the test to make sure when all given is valid then the User should be valid and saving of the User can be executed.
Add new test is valid
and the whole spec/models/user_spec.rb
should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
And all 10 tests, Integration and Model should pass:
Finished in 1.24 seconds
10 examples, 0 failures
Prettier RSpec Tests
By default the RSpec tests are represented as dots (.
) if they are passed and F
if they fail.
To display describe
, context
and it
titles while running RSpec, add format
to
.rspec
file:
1 2 |
|
Run all test and the format of RSpec test should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Conclusion
This post was all about the Rails Validation, there are few interesting samples how to test Rails application. The TDD in this post is solely done on User Model instead of on request browser based testes done with Capybara.
Now when we are sure that User Model validation logic is implemented and tested in next post, we will Save and Update User Views, Controller methods and create browser based test to make sure that User Model logic actually works in real usage!
Code
The code is hosted on GitHub and can be cloned from the xajler/just-todo-it.
Github xajler/just-todo-it commit for this post:
Implemented User Model validation and tested in user_spec.rb, the tests using users factory.