Develop the right mindset for Rails security
Avoid shipping vulnerable code by learning how to prevent security issues in your Rails applications.
Get the course for $99Hi, 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.