Testing

Unit test components using the render_inline test helper, asserting against the rendered output:

require "test_helper"

class ExampleComponentTest < ViewComponent::TestCase
  def test_render_component
    render_inline(ExampleComponent.new(title: "my title")) { "Hello, World!" }

    assert_selector("span[title='my title']", text: "Hello, World!")
    # or, to just assert against the text:
    assert_text("Hello, World!")
  end
end

(Capybara matchers are available if the gem is installed)

Note: assert_selector only matches on visible elements by default. To match on elements regardless of visibility, add visible: false. See the Capybara documentation for more details.

For debugging purposes, the rendered_content test helper outputs the rendered HTML.

Testing Slots

def test_render_component
  component = ListComponent.new(title: "Fruits").tap do |c|
    c.with_item { "Apple" }
    c.with_item { "Orange" }
    c.with_extra { "<div><span>rendered html</span></div>".html_safe }
  end

  render_inline(component)

  assert_selector("ul")
  assert_selector("li", text: "Apple")
  assert_selector("li", text: "Orange")
end

Previews as test cases

Since 2.56.0

Use render_preview(name) to render previews in ViewComponent unit tests:

class ExampleComponentTest < ViewComponent::TestCase
  def test_render_preview
    render_preview(:with_default_title)

    assert_text("Example component default")
  end
end

Testing components with behaviors

To test ViewComponents with behaviors, visit a preview in a system test:

class MyComponentSystemTest < ActionDispatch::SystemTestCase
  def test_default_preview
    visit("/rails/view_components/my_component/default")

    click_on("Open dialog")

    assert_text("Test Dialog")
  end
end

Best practices

Prefer testing the rendered output over individual methods:

# Good
assert_selector(".Label", text: "My label")

# Bad
assert_equal MyComponent.new.label, "My label"

Without capybara

In the absence of capybara, assert against the return value of render_inline, which is an instance of Nokogiri::HTML::DocumentFragment:

def test_render_component
  result = render_inline(ExampleComponent.new(title: "my title")) { "Hello, World!" }

  assert_includes result.css("span[title='my title']").to_html, "Hello, World!"
end

Slots

To test components that use Slots:

def test_renders_slots_with_content
  render_inline(SlotsComponent.new(footer: "Bye!")) do |component|
    component.with_title { "Hello!" }
    component.with_body { "Have a nice day." }
  end

  assert_selector(".title", text: "Hello!")
  assert_selector(".body", text: "Have a nice day.")
end

Action Pack Variants

Use the with_variant helper to test specific variants:

def test_render_component_for_tablet
  with_variant :tablet do
    render_inline(ExampleComponent.new(title: "my title")) { "Hello, tablets!" }

    assert_selector("span[title='my title']", text: "Hello, tablets!")
  end
end

Configuring the controller used in tests

Since 2.27.0

Component tests assume the existence of an ApplicationController class, which can be configured globally using the test_controller option:

config.view_component.test_controller = "BaseController"

To configure the controller used for a test case, use with_controller_class from ViewComponent::TestHelpers.

class ExampleComponentTest < ViewComponent::TestCase
  def test_component_in_public_controller
    with_controller_class PublicController do
      render_inline ExampleComponent.new

      assert_text "foo"
    end
  end

  def test_component_in_authenticated_controller
    with_controller_class AuthenticatedController do
      render_inline ExampleComponent.new

      assert_text "bar"
    end
  end
end

Setting request.path_parameters

Since 2.31.0

Some Rails helpers won’t work unless request.path_parameters are set correctly, resulting in an ActionController::UrlGenerationError.

To set request.path_parameters for a test case, use with_request_url from ViewComponent::TestHelpers:

class ExampleComponentTest < ViewComponent::TestCase
  def test_with_request_url
    with_request_url "/products/42" do
      render_inline ExampleComponent.new # contains i.e. `link_to "French", url_for(locale: "fr")`
      assert_link "French", href: "/products/42?locale=fr"
    end
  end
end

Setting request.host

Since 3.3.0

Rails routes that have a subdomain constraint require request.host to be set correctly.

To set request.host for a test case, use with_request_url from ViewComponent::TestHelpers:

class ExampleComponentTest < ViewComponent::TestCase
  def test_with_request_url
    with_request_url "/products/42", host: "app.example.com" do
      render_inline ExampleComponent.new # contains i.e. `products_path` that is constrained to the 'app' subdomain
      assert_link "Products", href: "/products"
    end
  end
end

Query parameters

Since 2.41.0

It’s also possible to set query parameters:

class ExampleComponentTest < ViewComponent::TestCase
  def test_with_request_url
    with_request_url "/products/42?locale=en" do
      render_inline ExampleComponent.new # contains i.e. `link_to "Recent", url_for(request.query_parameters.merge(filter: "recent"))`
      assert_link "Recent", href: "/?locale=en&filter=recent"
    end
  end
end

RSpec configuration

To use RSpec, add the following:

# spec/rails_helper.rb
require "view_component/test_helpers"
require "view_component/system_test_helpers"
require "capybara/rspec"

RSpec.configure do |config|
  config.include ViewComponent::TestHelpers, type: :component
  config.include ViewComponent::SystemTestHelpers, type: :component
  config.include Capybara::RSpecMatchers, type: :component
end

To access Devise’s controller helper methods in tests, add the following:

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :component

  config.before(:each, type: :component) do
    @request = vc_test_controller.request
  end
end

Specs created by the generator have access to test helpers like render_inline. For example:

require "rails_helper"

RSpec.describe ExampleComponent, type: :component do
  it "renders component" do
    render_inline(described_class.new(title: "my title")) { "Hello, World!" }

    expect(page).to have_css "span[title='my title']", text: "Hello, World!"
    # or, to just assert against the text
    expect(page).to have_text "Hello, World!"
  end
end

To use component previews:

# config/application.rb
config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"

Component system tests

Use with_rendered_component_path with render_inline to system test components:

class ViewComponentSystemTest < ViewComponent::SystemTestCase
  def test_simple_js_interaction_in_browser_without_layout
    with_rendered_component_path(render_inline(SimpleJavascriptInteractionWithJsIncludedComponent.new)) do |path|
      visit(path)

      assert(find("[data-hidden-field]", visible: false))
      find("[data-button]", text: "Click Me To Reveal Something Cool").click
      assert(find("[data-hidden-field]", visible: true))
    end
  end
end

For components that depend on a layout, provide the layout argument:

class ViewComponentSystemTest < ViewComponent::SystemTestCase
  def test_simple_js_interaction_in_browser_with_layout
    with_rendered_component_path(render_inline(SimpleJavascriptInteractionWithoutJsIncludedComponent.new), layout: "application") do |path|
      # ...
    end
  end
end