Shared examples with Minitest - Rails Tricks Issue 7

16 May 2023

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

Job listings

Post a Job for FREE!

Related posts