Test a model in Rails 5 using Rspec in 3 steps

Share this post!

I create a basic Rails 5 application, add an scaffold and create some basic tests on the model. I use RSpec for the test environment, Factory Girl for the factories and Faker to generate some test data. Ready? Let’s go!.

Step 1. Prepare the basic infrastructure

I’am using RVM to keep the Rails’ versions under control, but you can use other tool to do it; the important thing here is to choose Rails 5 in your environment.

So I create the app as usual.

$ rvm list
rvm rubies
ruby-1.9.3-p484 [ x86_64 ]
ruby-2.1.0 [ x86_64 ]
ruby-2.2.0 [ x86_64 ]
ruby-2.2.2 [ x86_64 ]
=* ruby-2.3.0 [ x86_64 ]
# => - current
# =* - current && default
# * - default
$ rails new basic_rspec

Then I can add the corresponding gems to Gemfile:

...
group :development, :test do
gem 'rspec-rails'
gem 'rails-controller-testing'
gem 'factory_girl_rails'
gem 'faker'
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
end
...
view raw Gemfile hosted with ❤ by GitHub

The next step is to install the gems, generate the basic RSpec infrastructure and create the scaffold for our Person class, the class I will use as example:

$ bundle install
Resolving dependencies...
Using rake 12.0.0
Using concurrent-ruby 1.0.2
Using i18n 0.7.0
...
(more gems here...)
...
Using web-console 3.4.0
Using rails 5.0.0.1
Using sass-rails 5.0.6
Bundle complete! 19 Gemfile dependencies, 72 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
$ rails generate rspec:install
Running via Spring preloader in process 4039
Expected string default value for '--jbuilder'; got true (boolean)
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
$ rails generate scaffold Person name:string age:integer
Running via Spring preloader in process 4046
Expected string default value for '--jbuilder'; got true (boolean)
invoke active_record
create db/migrate/20161207175605_create_people.rb
create app/models/person.rb
invoke rspec
create spec/models/person_spec.rb
invoke factory_girl
create spec/factories/people.rb
invoke resource_route
route resources :people
invoke scaffold_controller
create app/controllers/people_controller.rb
invoke erb
create app/views/people
create app/views/people/index.html.erb
create app/views/people/edit.html.erb
create app/views/people/show.html.erb
create app/views/people/new.html.erb
create app/views/people/_form.html.erb
invoke rspec
create spec/controllers/people_controller_spec.rb
create spec/views/people/edit.html.erb_spec.rb
create spec/views/people/index.html.erb_spec.rb
create spec/views/people/new.html.erb_spec.rb
create spec/views/people/show.html.erb_spec.rb
create spec/routing/people_routing_spec.rb
invoke rspec
create spec/requests/people_spec.rb
invoke helper
create app/helpers/people_helper.rb
invoke rspec
create spec/helpers/people_helper_spec.rb
invoke jbuilder
create app/views/people/index.json.jbuilder
create app/views/people/show.json.jbuilder
create app/views/people/_person.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/people.coffee
invoke scss
create app/assets/stylesheets/people.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss

I edit the model in order to add two validations and a new method ‘identifier’ that returns a concatenated string. The validations and the method will be used later on the tests.

# app/models/person.rb
Person < ApplicationRecord
validates :name, presence: true
validates :age, presence: true
def identifier
[name, age].join " "
end
end
view raw person.rb hosted with ❤ by GitHub

With this in place I migrate the database and prepare the tests. At this point I should be able to execute the test created by the scaffold (31 in total in this case).

$ rake db:migrate
== 20161207175605 CreatePeople: migrating =====================================
-- create_table(:people)
-> 0.0010s
== 20161207175605 CreatePeople: migrated (0.0010s) ============================
$ rake db:test:prepare
$ rspec
...
Finished in 1.58 seconds (files took 1.64 seconds to load)
31 examples, 0 failures, 17 pending

Step 2. Add the factories

Add the factories to create valid objects to be used in the tests. To do so create the ‘people.rb’ file (note the file name ‘people’, plural of ‘person’). I also include Faker to generate fake values on the object’s attributes.

# spec/factories/people.rb
require 'faker'
FactoryGirl.define do
factory :person do
name { Faker::Name.first_name }
age { Faker::Number.between(0, 100) }
end
end
view raw people.rb hosted with ❤ by GitHub

Step 3. Add the tests

I will verify the Person class has a valid factory, that it is not valid without a name and an age, and that the ‘identifier’ method returns a valid value. This shouldn’t be difficult.

# spec/models/person_spec.rb
require 'rails_helper'
RSpec.describe Person, type: :model do
it "has a valid factory" do
#person = FactoryGirl.build(:person) it is also valid
expect(FactoryGirl.build(:person)).to be_valid
end
it "is invalid without a name" do
person = FactoryGirl.build(:person, name: nil)
expect(person).not_to be_valid
end
it "is invalid without an age" do
person = FactoryGirl.build(:person, age: nil)
expect(person).not_to be_valid
end
it "returns a person's identifier as a string" do
person = FactoryGirl.build(:person, name: "Johnny", age: 12)
expect(person.identifier).to eq("Johnny 12")
end
end

Et voila! I can easily execute the test… with 0 failures!!

$ rspec spec/models/person_spec.rb
...
Finished in 0.39298 seconds (files took 2.32 seconds to load)
4 examples, 0 failures

Is this post useful for you? Would you improve/change something? Please add a comment and explain your idea to all of us. Thanks!

, ,