09 April 2021
RSpec is a wonderful tool for testing regular ruby and ruby on rails applications. However as an application and its test suite grows larger it can become cumbersome to run an entire suite or even an enire file when you make a change. Even switching between your editor and terminal to execute bundle exec rspec
can become tiresome if you are regularly running your specs or practicing Test Driven Development. Wouldn't it be wonderful if a tool existed that could automate the process of running your tests and only run them for files or tests that have actually changed?! Thankfully such a tool exists and it's called guard. The official docs summarise it better than I can.
Guard automates various tasks by running custom rules whenever file or directories are modified. It's frequently used by software developers, web designers, writers and other specialists to avoid mundane, repetitive actions and commands such as "relaunching" tools after changing source files or configurations.
Thankfully guard-rspec has been developed to allow us to integrate rspec with guard seamlessly and there are only a few steps required to get up and running on an existing rails app.
Firstly add guard-rspec to the development group in your Gemfile:
group :development do
gem 'guard'
end
Install it by running bundler:
bundle install
Generate a Guardfile
with default rspec configurations:
bundle exec guard init rspec
This command will generate a file that will look something like this:
# Guardfile
guard :rspec, cmd: "bundle exec rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)
# Rails files
rails = dsl.rails(view_extensions: %w(erb haml slim))
dsl.watch_spec_files_for(rails.app_files)
dsl.watch_spec_files_for(rails.views)
watch(rails.controllers) do |m|
[
rspec.spec.call("routing/#{m[1]}_routing"),
rspec.spec.call("controllers/#{m[1]}_controller"),
rspec.spec.call("acceptance/#{m[1]}")
]
end
# Rails config changes
watch(rails.spec_helper) { rspec.spec_dir }
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
# Capybara features specs
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
# Turnip features and steps
watch(%r{^spec/acceptance/(.+)\.feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
end
end
Now that we've got guard configured we need to actually use it. Previously if we wanted to run a test with rspec we would need to pass the file as an argument directly and every time we either changed the spec or the target file (in this case models/books.rb
) we would have to execute this command.
bundle exec rspec spec/models/books_spec.rb
Guard removes this hassle such that all we now need to do is start the guard process running with:
bundle exec guard
In your terminal you should now see confirmation that guard is running and listening for changes to your application:
16:19:58 - INFO - Guard::RSpec is running
Now if you make a change to models/books.rb
guard will automatically run the tests in spec/models/books_spec.rb
meaning you never need to re-enter your terminal and directly execute the rspec
command again!
Guard is highly customisable allowing you to specify which files to watch and which specs to trigger on a change to those files and even create custom watcher classes. For a full overview of how to customise guard and get the most out of its many features cheack out the official documentation.