Time to start writing some code! Without further ado, let’s build an awesome API with Rails 5.
First thing we need to do is install the rails
gem. You should already have Ruby and Rubygems installed.
Ensure you’re using Ruby 2.5.0 with the mrwa
gemset we created in the first chapter.
rvm gemset use 2.5.0@mrwa
Open a terminal and run the following command to install Rails 5.2 without any kind of documentation.
gem install rails --no-ri --no-rdoc --version="5.2.0"
We don’t need to download the documentation since we will be using the live version if we have any problem. We will also use a different testing framework than the one coming with Rails by default.
Next, we are going to create the application. Navigate to the folder we created in the introduction (master_ruby_web_apis
), create a new folder named module_02
, and run the command below from inside to generate a Rails 5 API-only application.
rails new alexandria --api --skip-test-unit
–api
specifies that we want to use the API mode. This will tell the Rails generators not to include anything unnecessary to build an API.
Move inside the created folder with cd
.
cd alexandria
If it was generated and you’re using RVM, open .ruby-version
at the root of the application and change the content from:
2.5.0
to:
2.5.0@mrwa
With this, our gemset will automatically be loaded.
Before we add any code, let’s give it a try and start the server.
rails server
Output
=> Booting Puma
=> Rails 5.x.x application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.4.0 (ruby 2.5.0), codename: Owl Bowl Brawl
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Open a web browser, head to http://localhost:3000 and you should see the Figure 1 popping on your screen.
Congratulations!
Now that we have a basic application, we are going to use Git to version it. Since we will also be deploying this application at the end of the module, let’s create a repository for it on GitHub. Setting up Continuous Integration and Delivery later on will allow us to get the code deployed simply by pushing to GitHub.
Git is a great tool that I’m hoping you are already using. If not, let me give you a quick introduction. From the Git website:
“Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.”
Basically, Git allows you to version your code and collaborate with other people on it. The best part is that it makes it very simple to do those two things. You will see how great Git is as we will be using it throughout this module for Alexandria.
There are many tools with a nice graphic interface to manage Git repositories. I personally like GitX because it’s pretty minimalistic; I only use it to stage changes and commit. For everything else, I use the command line because it gives me more control.
I know some people are huge fans of SourceTree, so give it a try if you’re looking for a tool to handle the entire Git flow in a graphical way.
If you already have your favorite, feel free to use it. However, the instructions in this book will be oriented towards running Git in a terminal to learn its various commands.
If you’ve installed Homebrew (as recommended in the previous chapters), you can just do the following.
brew install git
If you don’t want to use Homebrew (why not?), checkout this link to install Git.
Just use your packet manager to install Git. For Debian-based distribution, use the following command:
sudo apt-get install git
The first thing we need to do is tell Git that we want to version the current project. Inside master_ruby_web_apis/alexandria
, run the following command.
git init
Then we are going to stage all the files as ready to be committed.
git add .
Finally, we can commit the staged changes.
git commit -m "Initial Commit"
We’ll learn more about Git along the way.
Simply head to GitHub and sign up. Once you’ve confirmed your account, you will be able to create repositories. See Figure 2.
Once you’re logged in, create a new repository. See Figure 3 for reference. Name the repository (I called mine Alexandria) and add a quick description. Finally, click on Create Repository
.
Note that if you have a free account, you can only create Public repositories which are enough to follow this book. If you are a student, you can have unlimited repositories and a bunch of other cool stuff.
On the following page, you should see a list of steps as shown in Figure 4. Take note of the URL of your repository, we are going to need it. Mine is https://github.com/T-Dnzt/alexandria.git
for example.
We now have a GitHub repository and a local Git project, so we just have to link them together. To do this, we are going to add a remote
repository to our local version using the command below.
git remote add origin YOUR-GITHUB-REPO-URL
Replace YOUR-GITHUB-REPO-URL
with the actual URL you got in the previous section. For my repository, that would be:
git remote add origin https://github.com/T-Dnzt/alexandria.git
Finally, we can push all our local changes to the remote repository on GitHub using the git push
command.
git push origin master
Access https://github.com/T-Dnzt/YOUR-GITHUB-REPO-NAME
and you should see the list of all the files in our Rails project. See Figure 5 for reference.
We’ve got a working Rails application available on a remote Git repository. Now we can move on to the next topic - automated tests!
Writing tests is mandatory in good development teams. You can be the smartest developer in the world but your code will still have bugs. The important thing is catching those bugs as soon as possible, which is usually before they leave your computer.
Automated tests have a huge amount of benefits.
You fixed that annoying bug, but you didn’t write a test for it. Two weeks later, the problem is back because another developer has re-introduced it. Sound familiar? This could have been avoided by first writing a failing test demonstrating the issue to not only identify where the problem lies, but also to document it. The bug can then be fixed and the test will pass. If someone breaks it in the future, the test will start failing again and the regression won’t make it to production.
When you write automated tests, you identify different scenarios that could occur. Some of these scenarios will trigger unexpected behaviors in your code. The only way to find them is to either test the code manually or write automated tests. You definitely don’t want those scenarios to happen once the application is in production.
Anytime you change code, there is a risk that you will break something. Refactoring is very important but comes with the same risks as changing anything. Having automated tests checking the output of what you’re refactoring will let you make changes safely and with more confidence, leading to an improved code quality.
A lot of times, a piece of code is doing less or more than it should. Writing tests first, as well as following a TDD or BDD approach, lets you focus on writing the minimum amount of code necessary to make the test pass. This leads to simpler and smaller code.
When a new developer joins your team, you might constantly be afraid he is going to break something. If you’re not afraid, you should be. Changing code that you aren’t familiar with is hard and usually creates issues. To counter that, a strong testing suite is the best solution. With this tool in hand, newcomers can change things safely and will know anytime they break something.
Who wants to do manual testing, especially for a web API?. When we did it in the first module, and it was such a pain. Luckily, the application was small that we survived. For bigger projects, a testing suite can save a huge amount of time and prevents the need to manually test the new code. You should always do a bit of manual testing, (after all, tests can have bugs too), but once a feature and its automated tests are working there is no need to go back and test it.
Who wouldn’t want that? Since I’ve started writing tests for every piece of code I write, I’m more confident and way less stressed when deploying to production. I also sleep better at night and I’ve heard that it’s the same for my fellow devops engineers.
A Rails application is composed of many entities: models, controllers, views, serializers, presenters, and everything in between. Testing them requires writing tests in different styles.
Unit Tests can be used to test very tiny parts of your system in isolation. Those tests ensure that those parts implement the expected behavior without considering the whole project. Unit Tests are fast because they don’t need the whole system to run. One class and one method is enough to write a unit test.
In Rails, the tests we will write for models are unit tests for example.
Unit Tests are great to test things in isolation. But in the real world, all those things need to work together properly. That’s when integration tests come into play. Since they rely on more components and test systems at a higher level, they tend to be slower and bigger than unit tests.
Request tests in Rails are integration tests.
Unit tests are on one extreme side and integration tests are on the other. In between, there are all the other tests that don’t really fall into one of these categories. They don’t test the whole system or individual components - instead, they test a few components together, like controller & model, just like in Rails controller tests.
We will use unit tests to tests our Rails models and our custom Ruby classes but we won’t write controller tests.
Controller tests are unit tests focusing on testing the controllers in isolation, as simple Ruby classes and with all the required stubbing. I personally prefer to write request tests to test resources since they go through the whole Rails stack. That way, it’s simpler to write tests from a client’s perspective. It is easier and faster than ever to use request tests instead of controller tests and Rails 5 seems to be pushing towards that as well.
Since request tests are basically integration tests, our testing suite will be slower but that’s a drawback I’m ready to accept for Alexandria.
To test our Rails API, we will be using five different tools. Here is the list with quick descriptions. Some of those descriptions come directly from their documentation.
RSpec is a testing framework for Ruby that makes “TDD Production and Fun.” RSpec will be our main tool to write tests and will be incredibly useful to ensure that we write code that works.
“factory_bot
is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.”
“Database Cleaner is a set of strategies for cleaning your database in Ruby.
The original use case was to ensure a clean state during tests. Each strategy is a small amount of code, but is code that is usually needed in any Ruby app that is testing with a database.”
Webmock is a “library for stubbing and setting expectations on HTTP requests in Ruby.” We will use Webmock to ensure that our Rails endpoints work as expected.
“Shoulda Matchers provides RSpec- and Minitest-compatible one-liners that test common Rails functionality. These tests would otherwise be much longer, more complex, and error-prone.”
Alright, we have the list of tools we need. Now we can add them to our Gemfile
to get them loaded in our application.
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.5.0'
gem 'rails', '5.2.0'
gem 'sqlite3'
gem 'puma', '~> 3.11'
gem 'bootsnap', '>= 1.1.0', require: false
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'rspec-rails'
gem 'factory_bot_rails'
end
group :development do
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
group :test do
gem 'shoulda-matchers'
gem 'webmock'
gem 'database_cleaner'
end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
A quick bundle install
will get everything in place.
bundle install
Everything should be installed now. First, we’re going to use RSpec
generator to create the basic testing configuration we need to continue.
rails generate rspec:install
Output
Running via Spring preloader in process 32843
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
This generator just created 1 folder and 3 files:
.rspec
: This file contains the configuration for RSpec
- like how we want our tests to be colored, for example.
spec/
: This folder will contain all our tests.
spec/spec_helper
: The spec_helper
file contains the basic configuration required to run tests that don’t rely on Rails.
spec/rails_helper
: The rails_helper
, as the name suggests, is just like the spec_helper
file but for all tests that depend on the Rails stack.
In order to use all the gems we added with RSpec
, we need to add a bit of configuration to the rails_helper
file.
FactoryBot
DatabaseCleaner
Shoulda-Matchers
See the file below for the changes. You should be able to copy/paste this code once you’ve understood it.
# spec/rails_helper.rb
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
# Require RSpec for Rails, webmock and database_cleaner
require 'rspec/rails'
require 'webmock/rspec'
require 'database_cleaner'
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
begin
ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
puts e.to_s.strip
exit 1
end
RSpec.configure do |config|
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
# Load Factory Girl methods
config.include FactoryBot::Syntax::Methods
# Configure Database Cleaner
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
# Configure Shoulda-Matchers
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
end
To ensure that we got everything right, we can run the rspec
command from within our project. The expected output is presented below.
rspec
Output
Finished in 0.00028 seconds (files took 0.04333 seconds to load)
0 examples, 0 failures
Everything is ready for us to start writing code. We are going to use the RED-GREEN-REFACTOR
cycle to write our first tests. But what are we going to test?
For starters, our API is going to need a model to represent publishers - we can start with that. But before we write any test, let’s begin by creating the Publisher model and the associated table in the database.
The publishers
table will have 4 columns: id
, name
, created_at
and updated_at
. Since Rails takes care of the id
, created_at
and updated_at
columns, we can generate our model with the following command:
rails g model Publisher name:string
As you can see below or in your terminal, this command creates the model file, the migration, the test file in spec/
and the factory file in spec/factories
.
Running via Spring preloader in process 3854
invoke active_record
create db/migrate/TIMESTAMP_create_publishers.rb
create app/models/publisher.rb
invoke rspec
create spec/models/publisher_spec.rb
invoke factory_bot
create spec/factories/publishers.rb
Let’s open the migration file first, located at:
db/migrate/TIMESTAMP_create_publishers.rb
.
We don’t have anything to change here, but we can see that Rails has generated the migration we were expecting and added the name
attribute as a string.
# db/migrate/TIMESTAMP_create_publishers.rb
class CreatePublishers < ActiveRecord::Migration[5.2]
def change
create_table :publishers do |t|
t.string :name
t.timestamps
end
end
end
Let’s run this new migration in the development
and test
environments.
rails db:migrate && rails db:migrate RAILS_ENV=test
In Rails 5, all rake
commands, like rake db:migrate
for example, have been aliased with rails
. That means any rake
command can now be replaced with rails
for simplicity.
Now let’s take a look at the Publisher
model located at app/models/publisher.rb
. Pretty empty, huh? That’s fine for now - we’ll write tests before we add anything there.
# app/models/publisher.rb
class Publisher < ApplicationRecord
end
The generator has also created a factory for us. Open it (spec/factories/publishers.rb
) to have a look. Replace the default name with the name of a real publisher, like “O’Reilly”. This factory will be used to create instances of the Publisher
model with the values defined in this file.
# spec/factories/publishers.rb
FactoryBot.define do
factory :publisher do
name { "O'Reilly" }
end
end
Finally, we can see what’s inside the test file that was generated under spec/models/
for our Publisher
model.
# spec/models/publisher_spec.rb
require 'rails_helper'
RSpec.describe Publisher, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
end
There is nothing interesting there yet, but we are going to change that right now. Following the RED-GREEN-REFACTOR
cycle, we will first write a test for a feature we want in Alexandria. The first thing we need is a way to validate that a publisher always has a name.
Before we write our first test, there are a bunch of methods we need to understand. Most of these make it easier to organize and manage your code by grouping them. Let’s go through them.
describe
is used to define the target for a set of tests. describe '#valid?' do ... end
means that we are writing tests for the valid?
method.
context
allows us to define specific situations and write tests for each one of them. Take these two contexts for example:
context 'with valid params' do .. end
context 'with invalid params' do ... end
With those contexts, we can easily group tests that share the same context instead of naming our tests like it does something when given valid params
.
it
is used to define a test. It takes one parameter, the name of the test, and a block which contains the expectations.
expect
is a method used to define what the expected output is for a given input. You will understand how expect
works with all the tests we are going to create.
Originally, it was recommend to name your test using the should
verb as in it 'should work'
. These days, the best practice is to simply use the verb representing the expected behavior like it 'works'
.
To do this, we can use one of Shoulda-Matchers
matchers:
validate_presence_of
The whole test would be a one-liner as you can see below:
it { should validate_presence_of(:name) }
Using Shoulda-Matchers
allows us to write this kind of thing instead of the longer version, which can be seen below.
# We are not using Factory Girl for simplicity in those examples.
it "is invalid with a 'nil' name" do
publisher = Publisher.new(name: nil)
expect(publisher.valid?).to be false
end
it 'is invalid with a blank name' do
publisher = Publisher.new(name: '')
expect(publisher.valid?).to be false
end
it 'valid with a name' do
publisher = Publisher.new(name: "O'Reilly")
expect(publisher.valid?).to be true
end
Let’s add the one-liner we just wrote to the publisher_spec
file. We will also add another test that will be present for each one of our models. Since we will be using the factory for all our future tests, it is important to check that this factory allows us to create valid records.
# spec/models/publisher_spec.rb
require 'rails_helper'
RSpec.describe Publisher, :type => :model do
it { should validate_presence_of(:name) }
it 'has a valid factory' do
expect(build(:publisher)).to be_valid
end
end
Now we can start running our tests! Let’s just run the tests in the publisher_spec
file, since we don’t have any other tests anyway…
rspec spec/models/publisher_spec.rb
Failure (RED)
Failure/Error: it { should validate_presence_of(:name) }
Publisher did not properly validate that :name cannot be empty/falsy.
After setting :name to ‹nil›, the matcher expected the Publisher to be
invalid, but it was valid instead.
Finished in 0.05762 seconds (files took 1.82 seconds to load)
2 example, 1 failure
Our test clearly failed. That’s the first part of the cycle - now it’s time to fix it. In order to do that, we can simply add a validation to the Publisher
model. With validates: name, presence: true
added, our test should pass.
# app/models/publisher.rb
class Publisher < ApplicationRecord
validates :name, presence: true
end
Give it another try.
rspec spec/models/publisher_spec.rb
Success (GREEN)
.
Finished in 0.05325 seconds (files took 1.8 seconds to load)
2 example, 0 failures
Great, it’s working now! However, the default output for successful tests doesn’t say much except “.
”. We can change that by editing the .rspec
file and adding the –format documentation
option.
--color
--require spec_helper
--format documentation
If we run the tests again, we now have all the details.
rspec spec/models/publisher_spec.rb
Success (GREEN)
Publisher
should validate that :name cannot be empty/falsy
has a valid factory
Finished in 0.05501 seconds (files took 1.94 seconds to load)
2 examples, 0 failures
For the rest of this module, I will only put the last line of the output to keep things compact, but I recommend using the documentation
format.
Time to commit and push our changes to GitHub!
First, we need to know all the changes that have been made. We might want to be picky about what gets pushed.
git status
Output
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: Gemfile
modified: Gemfile.lock
Untracked files:
(use "git add <file>..." to include in what will be committed)
.rspec
app/models/publisher.rb
db/migrate/
db/schema.rb
spec/
no changes added to commit (use "git add" and/or "git commit -a")
It seems everything can simply be added.
git add .
Now, let’s see if all the files were correctly staged.
git status
Output
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .rspec
modified: Gemfile
modified: Gemfile.lock
new file: app/models/publisher.rb
new file: db/migrate/20160601060927_create_publishers.rb
new file: db/schema.rb
new file: spec/factories/publishers.rb
new file: spec/models/publisher_spec.rb
new file: spec/rails_helper.rb
new file: spec/spec_helper.rb
Commit with a commit message describing what we’ve done.
git commit -m "Setup testing environment"
Output
[master 24b96fe] Setup testing environment
10 files changed, 241 insertions(+), 37 deletions(-)
create mode 100644 .rspec
rewrite Gemfile (77%)
create mode 100644 app/models/publisher.rb
create mode 100644 db/migrate/20160601060927_create_publishers.rb
create mode 100644 db/schema.rb
create mode 100644 spec/factories/publishers.rb
create mode 100644 spec/models/publisher_spec.rb
create mode 100644 spec/rails_helper.rb
create mode 100644 spec/spec_helper.rb
And push the committed changes to GitHub.
git push origin master
Output
Counting objects: 19, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (17/17), done.
Writing objects: 100% (19/19), 5.05 KiB | 0 bytes/s, done.
Total 19 (delta 4), reused 0 (delta 0)
To https://github.com/T-Dnzt/alexandria.git
7dfa1a6..24b96fe master -> master
In this chapter, we’ve learned a lot! We created Alexandria, set up Git and took a deep dive into automated testing. We are now ready to proceed with creating models and writing more tests!