Hello and welcome to Advanced Ruby Testing Masterclass (minitest edition). In this course, I will present the most advanced topics in testing Ruby, Rails, and React-Rails applications using Minitest and Capybara, two premier tools for unit and end-to-end testing, respectively.
We will cover test-first philosophy, the mechanics of working with Minitest, how to structure your tests, and the purpose of testing. I’ll cover mocks, spies, fakes, and doubles and explain when to use mocking or stubs and when not to.
In the latter half of the course, we’ll get into Capybara, the powerful domain-specific language you will use to test your front-end applications in the browser. I’ll cover the all-important topic of race conditions, which is critical to understanding how to test Rails and React-Rails apps effectively. You will learn the secrets to dealing with race conditions in your test suite. These are secrets that very few people know about and understand, and in this course, you will get an in-depth look at how to deal with race conditions.
Remember, this course covers Minitest, not Rspec. If you are looking for a course that covers Rspec, please see my other course, which is also titled Advanced Ruby Testing, but the subtitle of that course is “The Rspec and Capybara Masterclass.” Please note that both courses cover overlapping material, and they are designed so you can purchase either one— you should buy only the Minitest course or the Rspec course, depending on which one you want to learn. Both courses follow the same curriculum except for specific parts where Minitest and Rspec differ— of course. There is Minitest-specific material in this course and Rspec-specific material in that course. However, all code examples have been written in Minitest for this course and in Rspec for the other course. When we get to the Capybara section of each course, the two courses are pretty much the same — except this course shows code examples in Minitest and the other course shows examples in Rspec.
Many of you may have a background in computer science, some of you may have learned at a boot camp, and many others watching this will be self-taught. This course is designed to be accessible and easy for all levels, but it is not a beginner-level course. I will not cover the basics of Ruby or Rails code. Although many of the examples in the course get into the mechanics of how these work, you should supplement this course with a beginner course in web development if you haven’t already done so.
You’re probably wondering who I am and why I created this course.
My name is Jason Fleetwood-Boldt, and I run a consulting agency in New York City that specializes in TDD and automated testing for Rails and React-Rails applications.
Many boot camp grads I’ve talked to have told me their boot camps did not cover TDD or testing. As well, while a lot of computer science undergraduate programs in the country have caught up quite a bit in the last several years; the majority of them still are not teaching TDD or application testing.
I find this very distressing because— well, for one, I love testing. But more importantly, learning how to test your code is hands down the most important thing you can learn to become a successful long-term developer in today’s competitive marketplace for software engineers.
In preparing for this course, I watched a lot of other course materials on the internet. What I found was that most of the content on the internet to teach people about code falls into one of two categories:
(1) Courses created by people who are very good educators but do not have significant real-world industry experience. These instructors’ courses are often very good at the beginner level but do not teach advanced topics like testing.
Alternatively, the other kind of course material out there are instructors who are very experienced in the industry but not very good educators. This material is either very high-level and not focused on the nuts and bolts of writing code, or they aren’t very good at breaking down concepts and presenting them in a digestible way.
So I built this course to fill a gap in the market: An advanced course that covers the topic of TDD and automated testing in-depth, uses real-world accessible examples, and is specifically written for Ruby and React-Rails applications.
There has been a long lineage of TDDers who came before me, and I owe much of my success to the path that they paved. Many of the most prominent TDDers were Java developers— a highly elite programming language found often in enterprise settings. While the cohort of Java TDDers did a lot of great work, there hasn’t been quite as much work done to evangelize TDD in the Rails and React-Rails world.
This is the course for you if you have learned some Ruby and have learned some React but aren’t confident about your testing skills. This is the course for you if you have been doing Rails for many years but have mostly written untested Rails code and are now trying to transition into being a sought-after specialist who writes tested code.
Let’s be frank: Frameworks come and go. Languages come and go. But there’s a reason why TDD and automated testing lasts: The reason is… it works!
I’ve worked for and consulted with numerous startups and big corporations. And in my work, I’ve noticed a consistent pattern: Tested codebases succeed, and untested codebases fail. I will get into more about why this is so predictable, but this fact is something that new engineers need to pay attention to right at the start of their careers.
What’s very unfortunate is that many companies with engineering teams do not have a strong testing culture and value system around TDD or automated testing. To be honest, most of the CEOs I’ve known do not understand what automated testing is. Many companies — even companies like Google— start their lives with very little or poor testing. Engineers at these companies have an uphill battle to bring about the transformational change needed to write tested code. Google was able to successfully bring about this change in 2005 using a company-wide newsletter called ‘Testing on the Toilet’ they distribute to this day in the bathroom stalls of all of the bathrooms in the company.
So this course is designed to give the beginner web engineer a north star: If you learn automated testing and follow the guidelines I will teach you, you will immediately become a more valuable engineer than if you write untested code.
Teams and companies that do not write tested code typically must hire more engineers who work under toxic and stressful conditions. I believe that companies who hire engineers who do not write tested codebases are a serious problem the tech industry is currently facing. Fortunately, most of these companies fail anyway, but the damage they do along the way is very real and very dangerous.
So, don’t fret: You’ve come to the right place. It’s a competitive industry, and becoming a top engineer requires a lot of smart work, but if you know how to test your code, you can get there and succeed. Many other ways you can learn will not give you the best tools. In this course, I will set you up for success, and you will succeed with TDD and automated testing.
How is this course Different?
You can learn TDD by watching Youtube videos, reading any of the books that came out of the Java-TDD era, from reading papers and tutorials. But what makes this course different?
What few tutorials there are typically are feature-based tutorials that teach you the feature set and configuration options of the testing framework option by option.
These tutorials are not really teaching what you must learn to become a successful Ruby tester who can write effective unit and end-to-end tests.
What you need to learn to become effective at testing are these skills:
• The philosophy of testing itself, why we are testing, and what we are testing
• How to work with factory data
• The mechanics of mocking and stubbing
• Understanding and working with race conditions
In this course, I’ve composited all of this education into a single class. Without a complete picture of what it looks like to use all of these tools effectively, it can be difficult to gain traction when trying to test your code.
So without further ado, let’s dive in.
- Minitest Masterclass (Part 1: Basic Ruby-only Setup, No Rails)
- Minitest Masterclass (Part 2: Minitest with Rails)
- Minitest Masterclass (Part 3: Philosophy of Testing)
- Minitest Masterclass (Part 4: Matchers)
- Minitest Masterclass (Part 5: Benchmarking)
- Minitest Masterclass (Part 6: FactoryBot and Factory Data)
7/ Mocks & Stubs & Doubles
When we’re practicing the craft of Ruby, we talk about talk “talking to your friends not strangers.”
In tests, we use special tools to make fake objects or methods stand-in for real ones, and sometimes spy on real objects. All of these tools together test doubles. A looser terminology — but inaccurate— is to call all 5 of these concepts mocking. You will often hear people say “mocking” when referring to any of the five concepts below.
Today, you’ll meet the five foundational test double objects you need to know about, and I’ll cover these over this chapter and the next.
Remember, the system under test means the code that is being tested, which you must be clear about before you start. By extension of the above axiom, we can extrapolate that anyone who is a ‘stranger’ — or object that is more than one object away from the system under test.
You already learned about factories, which should be your go-to for dummy data (but you can alsy write dummy data directly into test files).
In this chapter, we will go over stubs and mocks. In the next chaper, we’ll cover fakes and spies. Remember, all five of these are called “test doubles” and sometimes these five concepts are referred to “mocking.” (But saying this is confusing because a mock is distinct from its siblings below.)
dummy data | stub | mock | fakes | spies |
fake data that you will write directly in your test suite. Dummy data is typically passed into the system under test or | A stub is when you replace an existing method on an instance variable or a class so that the returned value will be something you supply. This is done to pass dummy data or dummy setup to the system under test. | A mock is a fake object that you will use in place of the real object. Remember, you don’t mock the system under test itself, only ancillary parts of the codebase | A fake object that actually implements the methods of the real object but is more appropiate for test operations. It should be ancillary to the system under test. | Use a real object, but spy on its methods so you can see examine what methods get called and assert against the pass arguments. Sometimes you use a spy to examine the system under test directly. When you do, be sure not to stub out any methods of the system under test itself. |
You already learned about a kind of dummy data when you learned about factories. | Don’t stub any method on the system under test, only the adjacent objects |
Minitest mock is a very lightweight utility for mocking and stubbing. It is generally not suitable for most apps at scale, so this course will instead teach you Mocha.
For Minitest: Meet Mocha
For creating mocks and stubs, this course will use Mocha.
As the mocha documentation says, mocha supplies and implementatio of mocks and stubs, not fakes and spies.
Mocha is intended to be used in unit tests for the Mock Object or Test Stub types of Test Double, not the Fake Object or Test Spy types. Although it would be possible to extend Mocha to allow the implementation of fakes and spies, we have chosen to keep it focused on mocks and stubs.
— Mocha documentation https://github.com/freerange/mocha
8/ Spies, Fakes, Doubles
Spies
https://github.com/ryanong/spy
my_spy = Spy.on(PersonMailer, :with).and_call_through
// do your action here
assert(my_spy.has_been_called_with?(user: user, alert: alert))
Fakes
The basic Fake with OpenStruct:
Doubles
10/ Race Conditions in Unit Tests
• Introduction to the all purpose wait helper
module WaitHelper
include Kernel
include Timeout
def wait_until(&block)
Timeout.timeout(1) do
until block.call
print '_'
sleep 0.1
end
@success = true
ensure
unless @success
raise "wait_until timed out on #{block.source} (wait block was defined at #{block.source_location}})"
end
end
end
end
11/ System Testing with Capybara
Visiting, selecting, and asserting
find()
• If you are using find and the thing is not on the page, an exception is raised
page
expect(page).to have_content
11/ External Web Requests
• If you VCR a set of external interactions — for example, retrieving a token and passing it to the front end which then in turn calls the 3rd party API— it is common for you to record tokens, which then fail on subsequent API calls. (see Plaid interaction)
WebMock
VCR
VCR.configure do |config|
# config.allow_http_connections_when_no_cassette = true
config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
config.hook_into :webmock
config.ignore_request do |request|
['127.0.0.1', 'chromedriver.storage.googleapis.com'].include? URI(request.uri).host
end
end
VCR EXAMPLE 1: openweatherapi
VCR EXAMPLE 2: Stripe
VCR EXAMPLE 3: Plaid
VCR EXAMPLE 4: ??????????
Faraday
12/ Capybara Drivers
• Capybara.using_session
• What are the capybara drivers
• The default drivers
• building your own driver
• Using a driver for a specific test
• Using a driver for a whole file.
13/ Debugging With Capybara
• Headless & Headed
save and open page
save and open screenshot
• Setting a debugging breakpoint
Selenium Setup & Browser options
Setting the window size, enabling chrome flags
• Setting Capybara.javascript_driver
• Using Capybara.using_session
– Seeing Your Console messages
wait_until do
page.evaluate_script("document.querySelector('label[for=\"birthday\"]') !== null")
end
page evaluate_script
page execute_script
• Seeing Your Console messages
• Using a scope block (within do)
Race conditions and wait Helpers
• The anatomy of a wait helper:
Get the start time
loop while the start time + the wait timeout is greater than the curret time
Look for the thing, be sure to use wait: 0
here or else that line will wait up to the Capy wait timeout, making your wait helper irrelevant
If the thing isn’t found, sleep 0.1 and loop around
If you exit loop and the thing was found, return the element to the calling code.
Raise an exception if you exit the loop and the thing wasn’t found.
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.
DateTime.new(created_at.year, created_at.month - 1, 20).to_date
fix:
last_month = (DateTime.new(created_at.year, created_at.month) - 1.month)
DateTime.new(last_month.year, last_month.month, 20).to_date
15/ Brittle Tests
“I touched something unrelated, and my tests broke.”
“Every change I make these tests break.”
16/ Race Conditions Deep Dive – Example 1
17/ Race Conditions Deep Dive – Example 2
18/ Race Conditions Deep Dive – Example 3
19/ Race Conditions Deep Dive – Example 4
20/ Race Conditions Deep Dive – Example 5
21/ Failing on CI but Not on Local
22/ The Virtuous TDD Cycle
23/ Testing Antipatterns
• Keep your tests debuggable
• Be careful about log file explosion — cut down on verbose logging. The Rails logger is an incredibly seductive trap: You can use it to overlog like a madman, making your life developing a constant stream of noise.
Turn on logging for specific things only when you need those particular things. Don’t put stuff into logs just because you think a future developer should or will see it— in fact, this one of the worst anti-patterns because noisy logs are more likely to be ignored.