TDD
Advanced
What is Hermetic
• Describe Blocks
• It Blocks
• Derived Class
Let’s assume we had an object called WebService
RSpec.describe YourApp::WebService do
1/ Install Rspec + Factory Bot
Add to Gemfile in a group marked :development and :test. Be sure to add it to a group for both :development and :test (do not add it to only a :test group).
gem 'rspec-rails'
gem 'factory_bot_rails'
then run bundle install
Run the rspec installer with
rails generate rspec:install
Now, notice that these files are created
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
Now delete your test/
folder with
rm -rf test/
(This removes the default folder for Minitest, which is the alternative to Rspec that we are not covering in this tutorial.)
Check-in this code using Git and give it a commit message of something like “installs Rspec”
2/ Meet the spec_helper.rb
and rails_helper.rb
For historical reasons, Rails-Rspec apps come with two default files: spec_helper and rails_helper.
All Rspec specs must begin with this exact line, at the very top of the file:
require "rails_helper"
Note that the rails_helper.rb
is inside of spec/
folder, even though your test might not be. When you run any rspec test, it runs as if the working directory is the spec/
folder itself, even when you are running a spec nested several folders deep. So all Rspec specs you write, including ones nested several folders deep, so do not try to use relative paths if your spec is nested several folders deep. Just include the line exactly as-is: require "rails_helper"
Both files contain Rspec configuration, but rails_helper.rb
contains configuration specific to Rails. Let’s take a look a the default spec/rails_helper.rb
file now.
First take note this is where RAILS_ENV is set to test. The ||= Ruby operator is called null coalescence and it means that if the RAILS_ENV is not set in the environment (ENV
global variable), then set it here.
Then we explicitly load any settings specific to the test environment itself. (These are found in config/environment/test.rb)
Pay attention to what this comment says: Rspec looks for any file inside of the the spec/
directory that ends with _spec.rb
. Those files will automatically be seen as spec files, but other files will not. If you can’t figure out why your spec isn’t running, make sure the file is named correctly.
Take a look for the top of the line that begins
RSpec.configure do |config|
A few lines down, you should see config.use_transactional_fixtures = true
Here is the important line that defines our use of transactional fixtures, as discussed in the introduction to this lesson. If you set this to false, you will switch Rspec into truncation strategy.
Towards the bottom of the RSpec.configure do |config|
you will see another important config:
The infer_spec_type_from_file_location!
setting. When enabled (which is just done by calling it with the bang method like you see here), specs will have added behaviors depending on what folder they are located in.
This is most relevant when we get to writing request specs and system specs, where we need either request spec helpers or Javascript, and so by putting the specs into these folders we are telling Rspec to run with either of these things.
Those are the most critical parts of the rails_helper
configuration, and in this tutorial, I will go over the default Rspec configuration only. Once you’ve finished the tutorial, feel free to explore the various settings.
Now that we have a Rspec installed, it is time to write your first spec. Exciting no?
3/ Your First Test
Let’s suppose we have some pencils. Pencils can be of different colors, and we can set Pencils to be any color we want.
We will first create Pencils as its own PORO — plain old Ruby object.
Let’s begin by writing a spec.
Run the rspec generator to create a pencil spec
rails generate rspec:model pencil
require 'rails_helper'
RSpec.describe Pencil, type: :model do
it "should create a pencil" do
expect(Pencil.new).to be_a(Pencil)
end
end
Then run your spec and watch it fail. You are now RED on the RED-GREEN-REFACTOR cycle.
Remember, always use the testing process to describe the code you want. In this fashion you will consistently describe the code you want first, then implement only as much code as to make your test pass.
To implement this spec, all we have to do is create a Pencil class at app/models/pencil.rb
class Pencil
end
Let’s go one step further and give our pencil a color attribute.
require 'rails_helper'
RSpec.describe Pencil, type: :model do
subject {Pencil.new}
it "should create a pencil" do
expect(subject).to be_a(Pencil)
end
it "should have a color attribute that will default to nil" do
expect(subject.color).to be(nil)
end
describe "#set_color" do
before(:each) do
subject.set_color("red")
end
it "should have a color attribute that can be set" do
expect(subject.color).to eq("red")
end
end
And our implementation code:
class Pencil
attr_accessor :color
def set_color(color)
@color = color
end
end
We are now GREEN in the RED–GREEN-REFACTOR cycle. This means that we can proceed to add new features by adding specs first, then implementing those features. We can also refactor the existing features with confidence knowing that there are automated specs covering expected results.
This is why it is called “RED-GREEN-REFACTOR” cycle. By continuously providing feedback to us the developer, the test cycle allows us to work exponentially smarter, which in turn gives us incredible powers to produce code that is open to being refactored and resilient to change.
Expect the Initializer to Take a Color Attribute
require 'rails_helper'
RSpec.describe Pencil, type: :model do
subject {Pencil.new}
describe "initialize" do
it "should create a pencil" do
expect(subject).to be_a(Pencil)
end
describe "with a custom initializer" do
subject {Pencil.new(color: "red")}
it "should have a color attribute that can be defined in the initializer" do
expect(subject.color).to eq("red")
end
end
end
describe "#set_color" do
before(:each) do
subject.set_color("red")
end
it "should have a color attribute that can be set" do
expect(subject.color).to eq("red")
end
end
end
class Pencil
attr_accessor :color
def initialize(* args)
@color = args[0][:color] if args[0] # a passive initializer (silently fails of no color)
end
def set_color(color)
@color = color
end
end
Refactoring Example (Address Denormalize)
4/ Your Rspec Workflow
Run the Next Failure
Rspec has a “Next failure” feature to shorten your workflow and make it efficient. To use this option (--only-failures
), you must first set `config.example_status_persistence_file_path` in your Rspec.configure
block.
rspec --next-failure
or rspec -n
for short
This tells RSpec to use the last complete run (which it stores in a file in tmp/) to determine the first failing test. It runs just that test.
The simplest, most lightweight workflow is to use this flag to fix run the first failing test, fix it, then run the next one. Wish, wash, repeat.
5/ Random Specs and the Random Seed
By default, your specs are run in random order. The randomness is determined by a random seed number, which is shown to you at the top of every RSpec run. RSpec will generate a new random number at the start of each run, show it to you, and then use this number to determine what order to run the specs in. If you re-run the specs using the same seed number, the specs will re-run in the same order.
This is considered best practice because specs should be independent of one another. Running them in a random order helps to find flakey tests that only pass when they are run in a specific order and fail in another order. This is important because it’s common (dangerously easy) to accidentally write specs that will have an order-dependancy, or fail when one before or after a specific other test.
When RSpec runs the test in random order and then it fails, how can you re-run the test in the exact same order again to debug the issue?
That can be done by telling RSpec to use the same seed for its randomness as it was used before.
RSpec tells you this seed when it is starting:
$ rspec spec Randomized with seed 29451 .....*.........
Here, re-run the specs in the exact same order run using the –seed flag with the same number that was generated randomly: 29451
$ rspec --seed 29451
6/ Dots and “Docs”
When you run RSpec by default, it will show you an output of green dots (or red ones for failing specs). Sometimes you may have specific specs which will hang or take
e a long time*, and thus you won’t be able to see which one is hanging. The t
*hanging is when a process does not terminate forever; for example, it is caught in an endless loop

To debug this kind of problem, you will want to set the flag -fdoc
This will tell Rspec to output the full documentation of what is run — listing out the test names themselves on the screen in red or green(*)
–format doc
COLORBLIND ACCOMMODATION ——
You can use this setting in Rspec.configure
to set your Rspec to use any color for success or failure. You also get options for pending, fixed, and detail.
For example, to keep the failure color as RED and make the success color blue — an easy solution for programmers with red/green colorblindness, add this line to your Rspec.configure
RSpec.configure do |config|
config.success_color = :blue
end
7/ Backtraces
To see the full backtrace of the crash, enable this in spec_helper.rb
config.full_backtrace = true
Or, just run your spec with --backtrace
8/ Factory Bot
Factory Bot is the premier Ruby tool for creating factories for your test suite.
Your factories will live in a folder at spec/factories
and will have the same names as the objects they represent.
Each time you build a factory (or create one, explained below), a new instance variable will come into Ruby’s memory space.
Build vs. Create
When you call build(:xyz)
it will be as-if you had called .new
on the thing you are creating — therefore, it won’t save it to the database.
When you call create(:xyz)
, you will be telling Factory Bot to instantiate and save your newly created object.
Build is typically preferred in Rails apps because it is much faster. It makes it so that your tests aren’t dependent on “hitting the database” (that means making any database calls)
This means that model-level validations will not be applied because the record won’t actually be saved.
Typically for much unit testing, where you are testing the internals of the code you are writing,
Often, however, there are necessary reasons to use create instead of build:
– When working with multiple objects, like in an integration test, ActiveRecord cannot save child records until the parent record is saved. This makes it necessary to either use create
(which instantiates and saves the new object), or use build
along with a save. (You can save only the child record too, which will
Initialization
initialize_with
skip_create
Factory Example (…)
9/ Coverage Reports with SimpleCov
10/ Mocking, Stubs, and Doubles
11/ The Testing Hourglass
12/ End-to-End Testing with Capybara
• Selenium Setup & Browser options
Setting the window size, enabling chrome flags
• Setting Capybara.javascript_driver
• Types of tests
• Mastering Race Conditions
• Using a scope block (within do)
• Using Capybara.using_session
• Seeing Your Console messages
https://stackoverflow.com/questions/46278514/capture-browser-console-logs-with-capybara/46292517#46292517
13/ Faking out Foreign Endpoints
Webmock
Faraday
14/ Flaky Tests
“Why do these tests fail randomly?”
“Why do these tests fail only after 7 pm at the end of the month?”
A common cause of flaky tests is time and date-based considerations.
15/ Brittle Tests
“I touched something unrelated, and my tests broke.”
“Every change I make these tests break.”