<- Back to the blog

Snapshot testing ViewComponents with RSpec

We make extensive use of ViewComponents in our Rails app. If you haven’t come across this new addition to the Rails toolkit, it takes some ideas from React and brings them to the rails party. It’s worth checking out. It has allowed us to rapidly build new interfaces and try out new ideas as we expand our product.

With the recommended way of testing, you need to add test attributes to your code, which can make the act of writing the test quite involved. We wanted to streamline this by taking inspiration from tools in the JavaScript world like jest snapshots and implement our own version of snapshot testing in RSpec. This type of testing is great for checking that you didn’t accidentally change something you didn’t mean to when adding a new feature or option to a component. The main drawback is since you are not asserting anything, you need to be diligent when creating snapshots! 

We already use rspec-snapshot gem elsewhere in our application to test generated reports, so it was just a matter of comparing HTML instead of CSV files! Here’s a quick tip on how we do it.

RSpec Helper

In the helper below, we use the after hook only target component type tests where snapshotting is enabled. We then use the metadata from the test case to compose the filename.

RSpec.configure do |config|
 config.include ViewComponent::TestHelpers, type: :component
 
 config.after(:each, type: :component, snapshot: true) do |example|
   class_name = example.metadata[:described_class].name.underscore
   test_name = example.metadata[:full_description].gsub(example.metadata[:described_class].name, "").gsub(" ","_")
   raise "component snapshot has no content" if rendered_component.blank?
   expect(rendered_component).to match_snapshot("#{class_name}/#{test_name}")
 end
end

If we detect that nothing has been assigned to rendered_component, we raise an error so the test fails. Otherwise, we use rspec-snapshot’s matcher match_snapshot to test everything was as expected.
Our test then looks like this:

RSpec.describe Table::SortHeaderComponent, type: :component do
 it "renders correctly without order", :snapshot do
   render_inline(described_class.new({
     label: "header",
     header_key: 'key',
     sort_key: 'key',
   }))
 end
  it "renders correctly with ascending order", :snapshot do
   render_inline(described_class.new({
     order: :asc,
     label: "asc header",
     header_key: 'key',
     sort_key: 'key',
   }))
 end
  it "renders correctly with descending order", :snapshot do
   render_inline(described_class.new({
     order: :desc,
     label: "desc header",
     header_key: 'key',
     sort_key: 'key',
   }))
 end
 
 it "renders correctly with a different sort key", :snapshot do
   render_inline(described_class.new({
     order: :desc,
     label: "desc header",
     header_key: 'key',
     sort_key: 'other_key',
   }))
 end
end

On the first run, this ends up creating a file structure and snapshots to compare against in the future:

├── __snapshots__
│   └── table
│   	└── sort_header_component
│       	├── _renders_correctly_with_a_different_sort_key.snap
│       	├── _renders_correctly_with_ascending_order.snap
│       	├── _renders_correctly_with_descending_order.snap
│       	└── _renders_correctly_without_order.snap
├── sort_header_component_spec.rb

Now if we want to update our snapshots we can just run the following command.

UPDATE_SNAPSHOTS=true bundle exec rspec ./spec/components/sort_header_component_spec.rb

Otherwise, it works just as the rest of our RSpec testing does. No doubt in the future someone will wrap this all up in a nice gem, but until then this quick solution is working well. Hope this is interesting to anyone out there exploring view components! If you end up using this technique in your teams, let us know on Twitter @trybearer!

We're hiring

Interested in challenges like this one?
Building a data security developer tool?

Learn more


Engineering
Share this article:

Bring data security to DevOps

Get a personalized demo to see how Bearer helps you implement your data security policy and mitigate risks of data leaks throughout the development lifecycle.