Minitest on Rails.

Grouping contexts with Minitest

Cover Image for Grouping contexts with Minitest
Dwight Watson
Dwight Watson

Context blocks are a useful RSpec feature for grouping related tests. Most frequently I use context blocks to group request specs by guest/authenticated/authorized groups to ensure my endpoints behave appropriately for each cohort. Here is a rudimentary example that reflects how I might previously test a Posts endpoint with RSpec. Generally I'd keep the expectations distinct so a single change should explain exactly what failed.

RSpec.describe "Posts", type: :request do
  context "as guest" do
    describe "POST #create" do
      it "does not create a new post" do
        expect {
          post posts_path, params: { post: { title: "Title" } }
        }.to_not change(Post, :cont)
      end

      it "returns forbidden response" do
        post posts_path, params: { post: { title: "Title" } }
        expect(response).to have_http_status :forbidden
      end
    end
  end

  context "as user" do
    before { sign_in create(:user) }

    describe "POST #create" do
      it "creates new post" do
        expect {
          post posts_path, params: { post: { title: "Title" } }
        }.to change(Post, :count).by(1)
      end
    end

    it "redirects to posts#show" do
      post posts_path, params: { post: { title: "Title" } }
      expect(response).to redirect_to post_url(Post.last)
    end
  end
end

How might we achieve this context idea in Minitest? I've been looking for options, but so far I've found and enjoyed using subclasses.

class PostsControllerTest < ActionDispatch::IntegrationTest
  class Guest < self
    test "should not create post" do
      assert_no_changes -> { Post.count } do
        post posts_path, params: { post: { title: "Title" } }
      end
      assert_response :forbidden
    end
  end

  class Authenticated < self
    setup do
      sign_in create(:user)
    end

    test "should create post" do
      assert_changes -> { Post.count } do
        post posts_path, params: { post: { title: "Title" } }
      end
      assert_redirected_to posts_url(Post.last)
    end
  end
end