I rarely if ever apply DRY principles to the tests I write. What might improve application code does not necessarily improve test code. Tests should be as verbose and repetitive as they need to be. If you can’t look at an individual test and understand exactly what’s going on without looking through several
before blocks or nested contexts, then something is probably wrong. It’s better to be a bit more verbose and repeat yourself a little than make your tests unreadable.
That said, sometimes you need to repeat nearly the exact same test over and over again. A good example would be for a
before_action in a controller that ensures a user is authenticated.
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base def current_user @current_user ||= User.find_by(id: session[:user_id]) end def authenticate redirect_to sign_in_path unless current_user end end
# app/controllers/monitors_controller.rb class MonitorsController < ApplicationController before_action :authenticate def index # ... end end
Every action that the
before_action runs before needs to be tested.
# test/controllers/monitors_controller_test.rb describe MonitorsController do describe "index" do it "requires authentication" do session[:user_id] = nil get :index assert_redirected_to sign_in_path end end end
You’d have to repeat almost this exact same test for every action, the only thing that would change is the controller and action being requested. This is a perfect candidate for a shared test.
If you’re familiar with RSpec you might know that it has a feature for this called shared examples. Minitest has no corresponding equivalent because sharing tests is an edge case. This pretty well highlights the fundamental difference between RSpec and Minitest: RSpec is everything you might ever need; Minitest is just what you’ll probably need. Since Minitest has no built-in facility for sharing tests, you can just implement your own.1
# test/test_helper.rb class Minitest::Spec def self.it_requires_authentication(&block) it "requires authentication" do session[:user_id] = nil instance_exec(&block) assert_redirected_to sign_in_path end end end
# test/controllers/monitors_controller_test.rb describe MonitorsController do describe "index" do it_requires_authentication do get :index end end end
No magic, it’s just Ruby.
- I’m defining the class method on
Minitest::Specbut where you define the class method depends on what framework you’re using and how you integrated Minitest into it. For Rails, the class you probably need to define it on is