March 14, 2018

Shared Components, Shared Integration Testing

At my job, we’re in the process of writing a library of shared React components. (This blog post deals with React, Ruby, and Selenium, but the concepts are applicable to anyone writing integration tests for a shared set of reusable components.) This is awesome, and we’re seeing high rates of adoption and modification among devs. However, when writing our integration specs, we end up writing the same selectors and behavior over and over. I came up with a solution this week for how to help improve some of the pain we’re feeling, and I wanted to share it with you:

Current Pain

  • Our integration tests are tightly coupled to the classnames and markup of our components.
  • If we want to do a green-green refactor of a core component (like, say, swapping out a third-party library for a home-rolled one, or vice versa), we need to refactor the integration tests as well.
  • There’s no one place to document the expected behavior of our core components.

Proposed Solution

  • Create spec helper objects that expose methods for manipulating and asserting things about the DOM that our components render.
  • Keep the knowledge of the actual shape of our HTML hidden inside these methods.
  • Put the spec helpers in the same folder as the components themselves.

An Example:

# core-components/paginator/paginator-helper.rb
module CoreComponents
  class Paginator
    include Capybara::DSL
    BASE_SELECTOR = ".core-paginator"

    def initialize(parent_selector = nil)
      @selector = parent_selector ? "#{parent_selector} #{BASE_SELECTOR}" : BASE_SELECTOR

    def go_to_next_page
      within(@selector) do

    def has_number_of_pages(number)
      within(@selector) do
        find(".page", count: number)

    def is_on_page?(page_number)
      within(@selector) do

# spec_helpers/pages/library.rb
module Pages
  class Library
    def book_paginator'.book-paginator')

# library_spec.rb
let(:library_page) { }

it 'can page between books' do

  expect(library_page.book_paginator).to be_on_page(1)
  expect(library_page.book_paginator).to have_number_of_pages(23)
  expect(library_page.book_paginator).to be_on_page(2)


  • Now, only one file knows about the shape of our core components, so that we’ll only have to change one file when we refactor the component.
  • As we move more toward the glorious world of building features out of mostly shared components, our testing will become easier and easier, as we’ll already have pre-built helpers that know what the component should be able to do. People will do what is easy, so let’s make TDDing easy.
  • While we could have a folder of core component helpers in our spec folder, who’s going to know to look there? Discoverability is key for driving adoption, and it’s much more likely that the next developer who goes to use a core component will see and use the spec helper when it’s in the same folder than if they don’t even know it exists.
  • If you have multiple instances of a core component on the same page (e.g. a datepicker), you’ll be able to pass the helper a selector for a parent div and have different instances of the helper. This should help reduce duplication in the main integration spec.