StimulusSpec
Drop-in RSpec matchers for hotwired/stimulus-rails — stop hand-rolling data-controller assertions and test your Stimulus wiring with expressive, purpose-built matchers.
- Request/controller specs —
have_stimulus_controller,have_stimulus_action,have_stimulus_target,have_stimulus_value,have_stimulus_class - Auto-included — zero setup required when
stimulus-railsis in your bundle - Configurable — disable auto-include when you need manual control
Companion gem to turbo_rspec — together they cover the full Hotwire testing stack.
Table of Contents
Installation
Add to your application's Gemfile:
group :test do
gem "stimulus_spec"
end
Setup
Rails + stimulus-rails (automatic)
No setup needed. When stimulus-rails is in your bundle, StimulusSpec::Matchers is automatically included in type: :request, :controller, :system, and :feature example groups.
Manual include
For non-Rails projects or custom contexts, include the matchers explicitly:
# spec/spec_helper.rb
RSpec.configure do |config|
config.include StimulusSpec::Matchers
end
Configuration
# spec/support/stimulus_spec.rb
StimulusSpec.configure do |config|
config.auto_include = false # disable automatic inclusion
end
Matchers
have_stimulus_controller
Assert that rendered HTML contains a data-controller attribute with the given controller name.
expect(response).to have_stimulus_controller("hello")
# Works with multiple controllers on a single element
expect(response).to have_stimulus_controller("clipboard")
# Negation
expect(response).not_to have_stimulus_controller("missing")
Uses space-separated token matching (~=), so it works correctly when multiple controllers are declared on a single element and won't partially match.
have_stimulus_action
Assert that rendered HTML contains a data-action attribute with the given action descriptor.
# Full descriptor
expect(response).to have_stimulus_action("click->hello#greet")
# Shorthand — matches any event
expect(response).to have_stimulus_action("hello#greet")
# Negation
expect(response).not_to have_stimulus_action("hello#disconnect")
have_stimulus_target
Assert that rendered HTML contains a data-{controller}-target attribute with the given target name.
expect(response).to have_stimulus_target("hello", "name")
expect(response).to have_stimulus_target("hello", "output")
# Negation
expect(response).not_to have_stimulus_target("hello", "missing")
have_stimulus_value
Assert that rendered HTML contains a data-{controller}-{name}-value attribute, optionally with a specific value.
# Assert the value attribute exists
expect(response).to have_stimulus_value("search", "url")
# Assert a specific value
expect(response).to have_stimulus_value("search", "url", "/results")
# Negation
expect(response).not_to have_stimulus_value("search", "url")
have_stimulus_class
Assert that rendered HTML contains a data-{controller}-{name}-class attribute, optionally with a specific class.
# Assert the class attribute exists
expect(response).to have_stimulus_class("search", "loading")
# Assert a specific class value
expect(response).to have_stimulus_class("search", "loading", "opacity-50")
# Negation
expect(response).not_to have_stimulus_class("search", "loading")
Example
RSpec.describe "Search", type: :request do
describe "GET /search" do
it "wires up the search controller" do
get search_path
expect(response).to have_stimulus_controller("search")
expect(response).to have_stimulus_action("input->search#query")
expect(response).to have_stimulus_target("search", "input")
end
end
end
Relationship to turbo_rspec
turbo_rspec includes basic Stimulus matchers (have_stimulus_controller, have_stimulus_action, have_stimulus_target). stimulus_spec goes deeper with value, class, and outlet matchers, plus richer failure messages and Stimulus-specific configuration. If you only need basic controller/action/target assertions alongside your Turbo matchers, turbo_rspec has you covered. If you want comprehensive Stimulus testing, use stimulus_spec.
Both gems can coexist — they use separate namespaces and won't conflict.
Contributing
Bug reports and pull requests are welcome on GitHub. See CONTRIBUTING.md for setup instructions, branch conventions, CHANGELOG requirements, and the PR checklist.
License
The gem is available as open source under the MIT License.