Hi, this week I will show how you can achieve a similar behavior to Rspec’s shared examples with Minitest. We will dry the minitest tests by extracting the common parts into modules.
Imagine you generated two scaffolds, one for a “User” object and one for an “Article” object. Your scaffold tests have almost the same code for most CRUD methods, so let’s share them between the tests. To achieve this, we will create a module:
# tests/controller/scaffold_extensions.rb
module ScaffoldExtensions
extend ActiveSupport::Concern
included do
test "should get edit" do
get url_for(controller: @subject.class.name.pluralize.downcase, action: :edit, id: @subject)
assert_response :success
end
end
end
This module uses ActiveSupport::Concern to have a friendlier interface to call methods when this module is included. At that point, we will create a test for displaying the edit page of the CRUD. Instead of naming the instance variable after the actual object, we will use a generic name @subject
, and to generate the URL, we will use the url_for
helper with the controller generated from the @subject
’s class.
We need to change both of our controller tests to set the instance variable and to include this module to test the edit endpoint:
# tests/controller/articles_controller_test.rb
...
require_relative "scaffold_extensions"
class ArticlesControllerTest < ActionDispatch::IntegrationTest
include ScaffoldExtensions
setup do
@subject = articles(:one)
end
...
We can remove the generated tests for this edit action from each file and run the tests. To dry our tests further, let’s extract the test for deleting, showing, and listing the records and for the new record action:
# tests/controller/scaffold_extensions.rb
module ScaffoldExtensions
extend ActiveSupport::Concern
included do
test "should get new" do
get url_for(controller: @subject.class.name.pluralize.downcase, action: :new)
assert_response :success
end
test "should get index" do
get url_for(controller: @subject.class.name.pluralize.downcase, action: :index)
assert_response :success
end
test "should get show" do
get url_for(@subject)
assert_response :success
end
test "should get edit" do
get url_for(controller: @subject.class.name.pluralize.downcase, action: :edit, id: @subject)
assert_response :success
end
test "should destroy" do
assert_difference("#{@subject.class.name}.count", -1) do
delete url_for(@subject)
end
end
end
end
Extracting the create and update tests are more involving because there are different attributes for each model, but with the help of other instance variables we can extract those too:
# tests/controller/articles_controller_test.rb
...
require_relative "scaffold_extensions"
class ArticlesControllerTest < ActionDispatch::IntegrationTest
include ScaffoldExtensions
setup do
@subject = articles(:one)
@create_params = { article: { title: @subject.title }}
@update_params = { article: { title: @subject.title }}
end
...
# tests/controller/scaffold_extensions.rb
...
test "should create" do
assert_difference("#{@subject.class.name}.count") do
post url_for(controller: @subject.class.name.pluralize.downcase, action: :create), params: @create_params
end
assert_redirected_to url_for(@subject.class.send(:last))
end
test "should update" do
patch url_for(@subject), params: @update_params
assert_redirected_to url_for(@subject)
end
...
I hope the above examples help to extract common parts of your minitest tests into shared examples.
Did you enjoy reading this? Sign up to the Rails Tricks newsletter for more content like this!
Or follow me on Twitter
I run an indie startup providing vulnerability scanning for your Ruby on Rails app.
It is free to use at the moment, and I am grateful for any feedback about it.If you would like to give it a spin, you can do it here: Vulnerability Scanning for your Ruby on Rails app!