Skip to content

Add RSpec matchers for turbo stream and turbo frames#2888

Open
bastin27 wants to merge 2 commits intorspec:mainfrom
bastin27:bastin27/add-turbo-hotwire-matchers
Open

Add RSpec matchers for turbo stream and turbo frames#2888
bastin27 wants to merge 2 commits intorspec:mainfrom
bastin27:bastin27/add-turbo-hotwire-matchers

Conversation

@bastin27
Copy link

@bastin27 bastin27 commented Feb 25, 2026

Summary

  • Add have_turbo_stream matcher to assert <turbo-stream> elements by action and target/targets, with optional .with_count(n) chaining
  • Add be_turbo_stream matcher to verify Turbo Stream content type (text/vnd.turbo-stream.html)
  • Add have_turbo_frame matcher to assert <turbo-frame> elements by id
  • Add has_turbo? feature check for conditional loading when turbo-rails is available
  • Update scaffold generator to include turbo_stream format tests for create, update, and destroy actions
  • CSS selector values are escaped to prevent selector injection in Nokogiri queries
  • Includes Cucumber feature specs and RSpec unit tests (36 examples)

Test plan

  • All 36 turbo matcher specs pass
  • Verify Cucumber feature specs pass with turbo-rails available
  • Test scaffold generator output includes turbo_stream format tests
  • Confirm matchers work in a real Rails 7+ app with Turbo enabled

Related issue: #2887

… frames

Rails 7+ uses Turbo as the default for form submissions and page updates,
but rspec-rails had no support for testing Turbo responses. This adds:

- `have_turbo_stream` matcher to assert turbo-stream elements in responses
- `be_turbo_stream` matcher to check Turbo Stream content type
- `have_turbo_frame` matcher to assert turbo-frame elements in responses
- `has_turbo?` feature check for conditional loading
- Updated scaffold generator to include turbo_stream format tests
- Cucumber feature specs and RSpec unit tests
Copy link
Member

@JonRowe JonRowe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 Sorry for the delay in looking at this, its something thats worth having but I feel prehaps that instead we should be leveraging the Rails assertations using:

match_unless_raises ActiveSupport::TestCase::Assertion do
  @scope.assert_... 
end

Style checks rather than re-building them in matchers...


def have_turbo_stream(**opts)
RSpec::Rails::Matchers::Turbo::HaveTurboStream.new(**opts)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, you should be able to call this already

RSpec.describe "have_turbo_frame matcher" do
def have_turbo_frame(id)
RSpec::Rails::Matchers::Turbo::HaveTurboFrame.new(id)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be available already

Comment on lines +92 to +97
context "with turbo_stream format", if: defined?(Turbo) do
it "creates a new <%= class_name %> and responds with turbo stream" do
post <%= index_helper %>_url, params: { <%= singular_table_name %>: valid_attributes }, as: :turbo_stream
expect(response).to be_turbo_stream
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be wrapped in an if statement that only emits the fragment if Turbo is enabled e.g. (note the -%> which remove the if line whitespace from the output)

Suggested change
context "with turbo_stream format", if: defined?(Turbo) do
it "creates a new <%= class_name %> and responds with turbo stream" do
post <%= index_helper %>_url, params: { <%= singular_table_name %>: valid_attributes }, as: :turbo_stream
expect(response).to be_turbo_stream
end
end
<% if RSpec::Rails::FeatureCheck.has_turbo? -%>
context "with turbo_stream format" do
it "creates a new <%= class_name %> and responds with turbo stream" do
post <%= index_helper %>_url, params: { <%= singular_table_name %>: valid_attributes }, as: :turbo_stream
expect(response).to be_turbo_stream
end
end
<% end -%>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants