Skip to main content

Testing with RSpec

Rage provides built-in RSpec integration for testing your API endpoints. After requiring rage/rspec, you get access to familiar Rails-style testing helpers.

Available Methods​

HTTP Request Helpers​

get(path, params: {}, headers: {})
options(path, params: {}, headers: {})
head(path, params: {}, headers: {})

post(path, params: {}, headers: {}, as: nil)
put(path, params: {}, headers: {}, as: nil)
patch(path, params: {}, headers: {}, as: nil)
delete(path, params: {}, headers: {}, as: nil)

Response Matchers​

  • have_http_status - Test response status codes
  • response.parsed_body - Access parsed response body

These helpers are available in request specs (type: :request) after requiring rage/rspec.

Setup Guide​

Step 1: Install Dependencies​

Add RSpec and Database Cleaner to your Gemfile:

Gemfile
group :test do
gem "rspec"
gem "database_cleaner-active_record"
end

Run bundle install to install the gems.

info

This example uses ActiveRecord. If you're using a different ORM, choose the appropriate Database Cleaner adapter.

Step 2: Initialize RSpec​

Run RSpec's initialization command to create configuration files:

rspec --init

This creates spec/spec_helper.rb and .rspec configuration files.

Step 3: Configure Database Cleaner​

Add these hooks to spec/spec_helper.rb inside the RSpec.configure block:

spec/spec_helper.rb
RSpec.configure do |config|
# Configure truncation strategy
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end

# Clean database between tests
config.around(:each) do |example|
DatabaseCleaner.cleaning(&example)
end
end
Important

The transaction cleaning strategy is currently not supported. This is because it relies on Database Cleaner starting a transaction on a connection and expecting the same connection to be used to create the records and query them in the controller. However, Rage executes requests in separate Fibers, causing Active Record to use different connections for the test and the controller, thus breaking this expectation.

Step 4: Write Your First Test​

Create a test file and require rage/rspec:

spec/requests/home_spec.rb
require "rage/rspec"

RSpec.describe "Home", type: :request do
it "returns a successful response" do
get "/"

expect(response).to have_http_status(:ok)
expect(response.parsed_body).to eq("It works!")
end
end

Run your tests:

rspec

Testing Examples​

Basic GET Request with Headers​

Test an endpoint that requires authentication:

spec/requests/api/v1/photos_spec.rb
require "rage/rspec"

RSpec.describe "Photos API", type: :request do
describe "GET /api/v1/photos" do
it "returns all photos" do
get "/api/v1/photos", headers: { "Authorization" => "Bearer my-test-token" }

expect(response).to have_http_status(:ok)
expect(response.parsed_body.count).to eq(5)
end
end
end

Sending JSON Data​

Test creating resources with JSON payloads:

spec/requests/api/v1/photos_spec.rb
require "rage/rspec"

RSpec.describe "Photos API", type: :request do
describe "POST /api/v1/photos" do
let(:photo_params) do
{ data: "base64_encoded_image", caption: "Beautiful sunset" }
end

it "creates a photo" do
post "/api/v1/photos", params: photo_params, as: :json

expect(response).to have_http_status(:created)
end

it "returns the newly created photo" do
post "/api/v1/photos", params: photo_params, as: :json

expect(response.parsed_body["data"]).to be_present
expect(response.parsed_body["caption"]).to eq("Beautiful sunset")
end
end
end

Testing Subdomain Constraints​

Test routes that are constrained to specific subdomains:

spec/requests/api/v1/photos_spec.rb
require "rage/rspec"

RSpec.describe "Photos API", type: :request do
before { host! "api.example.com" }

it "returns all photos" do
get "/api/v1/photos"

expect(response).to have_http_status(:ok)
expect(response.parsed_body.count).to eq(5)
end
end

Testing Rage::Cable​

Rage provides RSpec helpers for testing WebSocket channels. These helpers are similar to Rails Action Cable testing helpers with minor differences.

Available Methods​

Connection Helpers​

connect(path, headers: {})  # Emulate a WebSocket connection
stub_connection(identifiers) # Stub connection identifiers

Channel Helpers​

subscribe(params = {})       # Subscribe to the channel
perform(action, data = {}) # Perform a channel action

Matchers​

expect(subscription).to be_confirmed
expect(subscription).to have_stream_from("stream_name")
expect(subscription).to have_stream_for(model)

expect(connection).to be_rejected

Accessors​

subscription   # The current subscription instance
transmissions # Array of messages transmitted to the client
connection # The current connection instance

Testing Subscriptions​

Test that a channel correctly subscribes and sets up streams:

spec/channels/chat_channel_spec.rb
require "rage/rspec"

RSpec.describe ChatChannel, type: :channel do
let(:user) { create(:user) }

it "subscribes to the channel" do
stub_connection(current_user: user)
subscribe(room_id: 42)

expect(subscription).to be_confirmed
expect(subscription).to have_stream_from("chat_room_42")
end

it "streams for a specific model" do
stub_connection(current_user: user)
subscribe

expect(subscription).to have_stream_for(user)
end
end

Testing Transmissions​

Test messages sent to the client:

spec/channels/notifications_channel_spec.rb
require "rage/rspec"

RSpec.describe NotificationsChannel, type: :channel do
it "sends a welcome message on subscribe" do
subscribe

expect(subscription).to be_confirmed
expect(transmissions.last).to eq({ "message" => "welcome" })
end
end

Testing Broadcasts​

To test broadcasts, use RSpec's native mocking on Rage::Cable.broadcast:

spec/channels/chat_channel_spec.rb
require "rage/rspec"

RSpec.describe ChatChannel, type: :channel do
it "broadcasts messages to the channel" do
subscribe

expect(Rage::Cable).to receive(:broadcast).with("chat", { message: "hello" })
perform :send_message, { message: "hello" }
end
end
info

Unlike Rails, Rage does not provide the have_broadcasted_to matcher. Use expect(Rage::Cable).to receive(:broadcast) instead.

Testing Channel Actions​

Test custom actions defined in your channel:

spec/channels/chat_channel_spec.rb
require "rage/rspec"

RSpec.describe ChatChannel, type: :channel do
it "broadcasts typing indicator" do
subscribe

expect(Rage::Cable).to receive(:broadcast).with("chat", { status: "typing" })
perform :typing
end

it "uses connection identifiers in actions" do
stub_connection(current_user: "user_123")
subscribe(room_id: 456)

expect(Rage::Cable).to receive(:broadcast).with("info", { room_id: 456, user: "user_123" })
perform :info
end
end

Testing Connections​

Test connection authentication and rejection:

spec/channels/application_cable_spec.rb
require "rage/rspec"

RSpec.describe ApplicationCable::Connection, type: :channel do
it "connects with valid headers" do
connect "/cable", headers: { "Authorization" => "Bearer valid_token" }

expect(connection).not_to be_rejected
expect(connection.current_user).to eq("authenticated_user")
end

it "rejects connections without credentials" do
connect "/cable"

expect(connection).to be_rejected
end
end

Testing with Cookies and Session​

Use cookies and session helpers to set authentication data:

spec/channels/application_cable_spec.rb
require "rage/rspec"

RSpec.describe ApplicationCable::Connection, type: :channel do
it "connects with cookies" do
cookies["auth_token"] = "abc123"
connect "/cable"

expect(connection).not_to be_rejected
end

it "connects with encrypted cookies" do
cookies.encrypted["user_id"] = "999"
connect "/cable"

expect(connection.current_user).to eq("user_999")
end

it "connects with session data" do
session[:user_id] = "123"
connect "/cable"

expect(connection.current_user).to eq("123")
end
end

Testing with Query Parameters​

Pass query parameters through the connection path:

spec/channels/application_cable_spec.rb
require "rage/rspec"

RSpec.describe ApplicationCable::Connection, type: :channel do
it "uses query parameters for identification" do
connect "/cable?token=secret123"

expect(connection).not_to be_rejected
expect(connection.current_user).to eq("user_from_token")
end
end