Article for Junior developers on how to set up: Rails 5.2, RSpec 3.7, Factory Bot, Database Cleaner

Article was written 2018-08-09 and applies for current versions of gems

Configure fresh project

I’m assuming the reader has set up Ruby and Ruby on Rails on his computer after reading book Agile Web Development with Rails or https://guides.rubyonrails.org/getting_started.html

We are going to generate fresh Ruby on Rails project withot the native Ruby on Rails tests.

To display all options for fresh project you can lunch:

rails new -h

To generate fresh new project without Rails tests we will run:

rails new my_rspec_project_name --skip-test

Next open the Gemfile with your editor and insert into the section group :development, :test this gems:

# Gemfile # ... group :development, :test do # ... gem 'database_cleaner' gem 'factory_bot_rails' gem 'rspec-rails' gem 'faker' end # ...

It’s critical to have this gems in group :development, :test do not just group :test do as default commands like rails generate ... are running under development environment, therefore they will not pick up default config overrides by this gems unless you run: RAILS_ENV=test rails generate ...

Now install the gems:

bundle install

Now generate configuration files for RSpec

rails generate rspec:install

Now you should be able to generate Rails models with RSpec tests and FactoryBot factories:

rails generate model worker name:string age:integer

…after you lunch this generator you should see output like this:

invoke active_record create db/migrate/20180809131148_create_workers.rb create app/models/worker.rb invoke rspec create spec/models/worker_spec.rb invoke factory_bot create spec/factories/workers.rb

As you can see this also generated db migration file db/migrate/20180809131148_create_workers.rb with content:

class CreateWorkers < ActiveRecord::Migration[5.2] def change create_table :workers do |t| t.string :name t.integer :age t.timestamps end end end

So lets run the db migrations:

# migrations for develompent environment rake db:migrate # migrations for test environment RAILS_ENV=test rake db:migrate

…this will generate the workers databaset table

Let write some test

open the spec/models/worker_spec.rb write your first “failing” test:

require 'rails_helper' RSpec.describe Worker, type: :model do it do expect(1 + 200).to eq(0) end end

Now run rspec spec . You should see output like this:

Failures: 1) Worker should eq 0 Failure/Error: expect(1 + 200).to eq(0) expected: 0 got: 201 (compared using ==) # ./spec/models/worker_spec.rb:6:in `block (2 levels) in <main>' Finished in 0.00714 seconds (files took 0.59673 seconds to load) 3 examples, 1 failure, 1 pending

Great ! Now lets make this test pass. Change:

# ... expect(1 + 200).to eq(0) # ...

to:

# ... expect(1 + 200).to eq(201) # ...

Now you should see something like:

Finished in 0.00328 seconds (files took 0.61603 seconds to load) 3 examples, 0 failures, 1 pending

Don’t worry about the “pending” test. You can remove those later. What is important is that you have 0 failures . That means you have passing tests.

Working with Factories (FactoryBot)

https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md

Book Agile Web Development with Rails work with Fixture tests which valid way how to write tests but for purpouse of this and future tutorials we will use Factories .

You can read more about Factories in this article https://robots.thoughtbot.com/why-factories

We will use library FactoryBot

Before we start we need to configure one thing. Open spec/rails_helper.rb and add this line inside the RSpec configuration block:

# ... RSpec.configure do |config| # ... config.include FactoryBot::Syntax::Methods end

This will ensure we can work with Factory Bot syntax natively in our RSpec tests.

Open the spec/factories/workers.rb file and change autogenerated content from:

FactoryBot.define do factory :worker do name "MyString" age 1 end end

to

FactoryBot.define do factory :worker do name "Ezo" age 31 end end

Now open the spec/models/worker_spec.rb and add one more test:

require 'rails_helper' RSpec.describe Worker, type: :model do # .... describe 'default worker details' do let(:worker) { create :worker } it 'should initialize worker with name and age' do expect(worker.name).to eq("Ezo") expect(worker.age).to eq(31) end end describe 'default worker details' do before do create :worker end it 'should initialize worker with name and age' do expect(Worker.count).to eq 1 w = Worker.last expect(w.name).to eq("Ezo") expect(w.age).to eq(31) end end end

If you run rspec spec all tests should pass 4 examples, 0 failures, 1 pending

Now these are just some stupid tests that will create a Worker in our database with some attributes and we just test if the attributes have those values.

In reality you will be testing more complex logic with RSpec such as

def trigger_money_transfer(account) account.balance = account.balance + 800 end # ... let(:bank_account) { create :account } it "should transfer buch of money to my Account" do expect(bank_account.balance).to eq 0 trigger_money_transfer(bank_account) expect(bank_account.balance).to eq 800 end # ...

B.T.W. FactoryBot is a rewrite of older gem with the name FactoryGirl If you stumble upon any FactoryGirl mentions on the internet most of the functionality will work on FactoryBot

Database cleaner

DatabaseCleaner is a gem that will help you keep your database without records before every test run.

All you need to do is in spec/rails_helper.rb and add this lines inside the RSpec configuration block:

# ... RSpec.configure do |config| # ... config.before(:suite) do DatabaseCleaner.strategy = :deletion end config.before(:each) do |example| DatabaseCleaner.clean end end

In our context it will delete records from test DB before every test

There is more to this gem, there are different strategies so that the entire test suite is faster. Read more at https://github.com/DatabaseCleaner/database_cleaner

Faker

https://github.com/stympy/faker

Sometimes you want to have random data in your tests (E.g. random email address) as that helps you discover problems you would normally spot only in production when real data starts pouring into your system.

You can generate random data by using FactoryBot sequence syntax:

FactoryBot.define do factory :worker do sequence :name do |n| "Ezo#{n}" end age 31 end end

That will produce:

Ezo1 Ezo2 Ezo3 # ...

But still you will end up with pretty simmilar data in your system. There is a better way how to have truly random data with Faker gem:

Faker::Name.first_name # => "Jonathan" Faker::Name.first_name #=> "Luigi" Faker::Name.first_name #=> "Crissy" Faker::Number.number(2) #=> "75" Faker::Number.number(2) #=> "48"

Faker offers lot of different “fake data” types. You can explore them here. Try to play around with variations to see what feels right.

What is common in Rails world is to combine Faker with Factory bot for generating random data:

in spec/factories/workers.rb

FactoryBot.define do factory :worker do name { Faker::Name.first_name } age 31 end end

So our test could look like ( spec/models/worker_spec.rb )

require 'rails_helper' RSpec.describe Worker, type: :model do # .... describe 'default worker details' do let(:worker) { create :worker } it 'should initialize worker with name and age' do expect(worker.name).to be_kind_of(String) expect(worker.name).to worker.name end end end

Now again this is useless test not helping the developer much, I just want to show you that when you are dealing with random data you cannot just compare whether the result equals a string. You need to check if the result equals the state and type of an object.

You don’t want to have Random data all the time, I would even argue that most of the time it’s healthier to work with dereministic data. To understand why pls read this article

Discussion