1. Introduction
Ruby on Rails and automated testing go hand in hand. Rails ships with a built-in test framework. It automatically creates boilerplate test files when you use generators, ready for you to fill in with your own tests. That’s pretty awesome, if you ask me.
Yet, many people developing in Rails are still either not testing their projects at all, or at best, only adding a few token specs on basic things that may not even be useful, or informative. Perhaps working with Ruby or opinionated web frameworks is a novel enough concept, and adding an extra layer of work seems like just that–extra work! Or maybe there is a perceived time constraint–spending time on writing tests takes time away from writing the features our clients or bosses demand. Or maybe the habit of defining test as the practice of clicking links in the browser is just too hard to break.
I’ve been there. As an engineer, I have problems to solve. And, typically, I find solutions to these problems in building software. I’ve been developing web applications since 1995. For a long time, I worked as a solo developer on shoestring, public sector projects. Aside from some structured exposure to BASIC as a kid, a little C++ in college, and a wasted week of Java training in my second grown-up job outside of college, I’ve never had any honest-to-goodness schooling in software development. In fact, it wasn’t until 2005, when I’d had enough of hacking ugly spaghetti-style PHP code, that I sought out a better way to write web applications.
I’d looked at Ruby before, but never had a serious use for it until Rails began gaining steam. There was a lot to learn–a new language, an actual architecture, and a more object-oriented approach. Even with all those new challenges, though, I was able to create complex applications in a fraction of the time it took me in my previous, framework-less efforts. I was hooked!
Confident testing
But early Rails books and tutorials focused more on development speed (build a blog in 15 minutes!) than on good practices like testing. If they covered testing at all, they generally reserved it for a chapter toward the end. Newer educational resources on Rails have addressed this shortcoming, and demonstrate how to test applications from the beginning. Countless books now exist specifically on the topic of testing. But without a sound approach to the testing side, many developers–especially those in a similar position to the one I was in–may find themselves without a consistent testing strategy. If there are any tests at all, they may not be reliable, or meaningful. Such tests don’t lead to developer confidence.
My first goal with this book is to introduce you to a consistent strategy that works for me–one that you can then, hopefully, adapt to make work consistently for you, too. If I’m successful, then by reading this book, you’ll test with confidence. You’ll be able to make changes to your code, knowing that your test suite has your back and will let you know if something breaks.
Why RSpec?
I have nothing against other Ruby testing frameworks. If the default MiniTest framework helps you confidently write sustainable test suites, that’s great!
For me, RSpec’s capacity for specs that are readable, without being overly cumbersome, is a winner. I’ll talk more about this later in the book, but I’ve found that with a little coaching, even most non-technical people can read a spec written in RSpec and understand what’s going on. It’s expressive in such a way that using RSpec to describe how I expect my software to behave has become second nature. The syntax flows from my fingers, and is pleasant to read in the future when I’m making changes to my software.
My second goal with this book is to help you feel comfortable with the RSpec functionality and syntax you’re most likely to use on a regular basis. RSpec is a complex framework, but like many complex systems, you’ll often find yourself using 20 percent of the available functionality for 80 percent of your work. With that in mind, this is not a complete guide to RSpec or companion libraries like Capybara. It instead focuses on the tools I’ve used for years to test my own Rails applications. It will also introduce you to common patterns so that, when you run into an issue that’s not covered in the book, you’ll be able to intelligently look for solutions and not get stuck.
Who should read this book
If Rails is your first foray into a full-stack application framework, and your past programming experience didn’t involve any testing to speak of, this book will hopefully help you get started. If you’re really new to Rails, you may find it beneficial to review coverage of development and basic testing. My favorite is Agile Web Development with Rails 7 by Sam Ruby; lots of people like Michael Hartl’s Rails Tutorial as well. My book assumes you’ve got some basic Rails skills under your belt. In other words, it won’t teach you how to use Rails, and it won’t provide a ground-up introduction to the testing tools built into the framework. Instead, we’re going to be installing RSpec and a few extras to make the testing process as easy as possible to comprehend and manage. So if you’re new to Rails, check out one of those resources first, then come back to this book.
If you’ve been developing in Rails for a little while, but testing is still a foreign concept, then this book is for you! I was in your position for a long time, and the techniques I’ll share here helped me improve my test coverage and think more like a test-driven developer. I hope they’ll do the same for you.
Specifically, you should probably have a grasp of
- Server-side Model-View-Controller application conventions, as used in Rails
- Bundler for gem dependency management
- How to work with the Rails command line
If you’re already familiar with using MiniTest, or even RSpec itself, and already have a workflow in place that gives you confidence, you may be able to fine-tune some of your approach to testing your applications. I hope you’ll also learn from my opinionated approach to testing, and how to go from testing your code to testing with purpose.
This is not a book on general testing theory, and it doesn’t dig too deeply into maintenance and performance issues that can creep into legacy software over time. Other books may be of more use on those topics. Refer to More Testing Resources for Rails at the end of this book for links to these and other books, websites, and testing tutorials.
My testing philosophy
What kind of testing is best–unit tests, or integration? Should I practice test-driven development, or behavior-driven development (and what’s the difference, anyway)? Should I write my tests before I write code, or after? Or should I even bother to write tests at all?
Discussing the right way to test your Rails application can invoke major shouting matches amongst programmers. Yes, there is a right way to do testing–but if you ask me, there are degrees of right when it comes to testing. My approach focuses on the following basic beliefs:
- Tests should be reliable.
- Tests should be easy to write.
- Tests should be easy to understand–today, and in the future; by you and your collaborators.
In summary: Tests should give you confidence as a software developer. If you mind these three factors in your approach, you’ll go a long way toward having a sound test suite for your application–not to mention becoming an honest-to-goodness practitioner of Test-Driven Development.
Yes, there are some tradeoffs–in particular:
- We’re not focusing on speed, though we will talk about it later.
- We’re not focusing on overly DRY code in our tests. But in tests, that’s not necessarily a bad thing. We’ll talk about this, too.
In the end, though, the most important thing is that you’ll have good tests–and reliable, understandable tests are a great way to start, even if they’re not quite as optimized as they could be. This is the approach that finally got me over the hump between writing a lot of application code, calling a round of browser-clicking “testing,” shipping to production, and hoping for the best. There’s a better way: Taking advantage of a fully automated test suite and using tests to drive development and ferret out potential bugs and edge cases before users spot them.
And that’s the approach we’ll take in this book.
How the book is organized
In Testing Rails with RSpec I’ll walk you through taking a basic Rails application from completely untested to respectably tested with RSpec. The book covers Rails 7.1 and RSpec 3.12. Many of the concepts apply to older and newer versions of each, albeit with slightly different syntax.
The book starts with RSpec installation in an existing Rails application. From there, we build a test suite bit by bit, starting with small, isolated tests and working out to broader, more complex testing scenarios. This is the same, step-by-step process I used to get better at testing my own software. I strongly recommend working through the exercises in your own applications–it’s one thing to follow along with a tutorial; it’s another thing entirely to apply what you learn to your own situation. We won’t be building an application together in this book, just exploring code patterns and techniques. Take those techniques and make your own projects better!
Downloading the sample code
You can grab the sample code at https://everydayrails.com/rspecbook. The provided Zip file contains snapshots of the sample application and its test suite as it grows, chapter to chapter. For example, the 02-models directory includes the Rails application itself, and the tests added in chapter 2.
Code conventions
I’m using the following setup for this application:
- Rails 7.1: The latest version of Rails is the big focus of this book. As far as I know, most of the techniques I’m using will apply to any version of Rails from 6.0 onward. Your mileage may vary with some of the code samples, but I’ll do my best to let you know where things might differ.
- Ruby 3.2: Any version of Ruby 3 should work.
- RSpec 3.12: RSpec 3.0 was released in spring, 2014, and has been largely stable since that time. Earlier versions used a significantly different syntax we won’t cover in this edition of the book.
If something’s particular to these versions, I’ll do my best to point it out. If you’re working from an older version of Rails, RSpec, or Ruby, previous versions of the book are available as free downloads through Leanpub with your paid purchase of this edition. They’re not feature-for-feature identical, but you should hopefully be able to see some of the basic differences.
Again, this book is not a traditional tutorial! The code provided here isn’t intended to walk you through building an application. It’s here to help you understand and learn testing patterns and habits to apply to your own Rails applications. In other words, you can copy and paste, but it’s probably not going to do you a lot of good in the long run.
Discussion and errata
I’ve put a lot of time and effort into making sure Testing Rails with RSpec is as error-free as possible, but you may find something I’ve missed. If that’s the case, head on over to the issues section for the source on GitHub to share an error or ask for more details: https://github.com/everydayrails/rspec-sample-rails-7.1/issues
A note about gem versions
The gem versions used in this book and the sample application are current as I write this Rails 7.1 edition, in 2024. Of course, any and all may update frequently, so keep tabs on them on Rubygems.org, GitHub, and your favorite Ruby news feeds for updates.
A note about styling
Many of the code samples included in this book are based from code created by generators. As a result, you may see samples with mismatching styles–for example, use of single quotes and double quotes in the same file. In the interest of keeping differences between what’s generated and what matters for learning RSpec, I’ve elected to leave styles in generated code as-is, but will otherwise generally follow conventions as defined by Standard Ruby.
About the sample application
Meet TasteDrivenDishes, the latest social site for finding and sharing your favorite recipes with users around the world! We’d better get a test suite in place before the site hits the front page of Hacker News–better for us to catch (and fix) bugs before our users and investors do.
To start, TasteDrivenDishes includes the following features:
- A user can browse recipes and filter by category.
- A user can create an account to add their own recipes.
- A signed-in user can mark recipes as favorites.
- A user’s account has an avatar, provided by the Dicebear service.
- A developer can access a public API to develop external client applications.
Up to this point, I’ve intentionally avoided writing tests for the application (see the 01-untested folder in the sample code download). This means I have a test directory full of untouched test files and data setup. I could run bin/rails test, and perhaps some of these tests would even pass. But since this is a book about RSpec, we’ll delete this folder, set up Rails to use RSpec instead, and build out a reliable test suite. That’s what we’ll walk through in this book.
First things first: We need to configure the application to recognize and use RSpec. Let’s get started!