Blog

Thoughts from my daily grind

RSpec (v6) Rails Request Specs

Posted by Ziyan Junaideen |Published: 25 June 2023 |Category: Ruby on Rails
Default Upload |

Request specs are a thin wrapper around Rails integration tests. Similar to controller tests, request tests allow you to test controller actions. Unlike controller tests, request tests involve routing, middleware, and rack requests/responses.

An RSpec test can be marked as a request spec by adding meta-data type: :request. This is not required if RSpec is configured to infer_spec_type_from_file_location. In this case, you can add a request spec to the spec/requests folder and skip the meta-data.

# spec/requests/posts_spec.rb
RSpec.describe "Posts requests" do
end

# spec/any/other/location/posts_spec.rb
RSpec.describe "Posts requests", type: :request do
end

Assertions

  • render_template
  • redirect_to

Capybara

Capybara is not intended to be used with Rails request specs and is not supported. However, you could use Capybara with Feature specs.

Example

it "creates a Post and redirects to the Post's page" do
  post api_posts_path, params: { post: { title: "RSpec (v6) Rails Request Specs",
                                         body: "Request specs are a thin wrapper around Rails integration tests." } }

  expect(response).to redirect_to(assigns(:post))
  follow_redirect!

  expect(response).to render_template(:show)
  expect(response.body).to include("RSpec (v6) Rails Request Specs")
end

Requesting a specific content type

The default content type is HTML. Should you need to test against specific content types (ex: turbo stream, JSON etc) you can specify the header.

# Calling an endpoint responding to Turbo Stream
it "loads a post" do
  get post_path(current_post, format: :turbo_stream)
  expect(response).to have_http_status(:ok)
  expect(response.content_type).to eq("text/vnd.turbo-stream.html; charset=utf-8")
end
# Calling an endpoint responding to JSON
it "loads a post" do
  get post_path(current_post, format: :json)
  expect(response).to have_http_status(:ok)
  expect(response.content_type).to eq("application/json; charset=utf-8")
end

Submitting specific content type

Let's assume you intend to submit data to a JSON endpoint. You can provide the data as a JSON string, or better, use a hash and convert it to JSON.

it 'creates a post' do
  post api_posts_path, params: { post: { title: "RSpec (v6) Rails Request Specs",
                                         body: "Request specs are a thin wrapper around Rails integration tests." } },
                       headers: { "ACCEPT" => "application/json" }

  expect(response).to have_http_status(:created)
  expect(response.content_type).to eq("application/json; charset=utf-8")
end

Testing subdomains

Most modern applications utilise subdomains. You need to use the host! method to use a specific subdomain.

before { host! "api.example.com" }

it "creates a Widget" do
  post api_posts_path, params: { post: { title: "RSpec (v6) Rails Request Specs",
                                         body: "Request specs are a thin wrapper around Rails integration tests." } },
                       headers: { "ACCEPT" => "application/json" }

  expect(response).to have_http_status(:created)
  expect(response.content_type).to eq("application/json; charset=utf-8")
end
About the Author

Ziyan Junaideen -

Ziyan is an expert Ruby on Rails web developer with 8 years of experience specializing in SaaS applications. He spends his free time he writes blogs, drawing on his iPad, shoots photos.

Comments