System tests are great because they allow you to write tests that cover the entire app as if a user was sitting in front of it using it with a real keyboard and mouse. Click on this, fill out that form, make real ajax requests with real elements and write tests against real user behavior. They're great, and I highly suggest you have at least a few of them covering the use cases for your Rails app. I recently ran into an issue with testing using React and Rails and wanted to share my findings here. Thanks to everyone who helped along the way!

The biggest issue is that we have a modal that appears after the user clicks a button. It's animated, and presents an input field. It's super easy to test if the modal renders and to click the button, but we show a window.alert if the user tries to submit the form without filling out the name field. Testing for that case was easy. Updating the input to have a value was too, but actually convincing React to fire it's onChange handler for the Input component was very tricky.

Before I discovered this was going to be a longer road, I tried to use fill_in '#name_input_field', with: "Testing", but this didn't work. The save_screenshot feature of Capybara came in handy and helped me notice that the text field never had any value (even though it was highlighted and focused in the screenshots). It was super confusing.

At first I tried doing the following:

var input = document.getElementById('name_input_field');
input.value = "Testing";
input.dispatchEvent(new Event('change'));

It didn't work. It took a while to figure out why, but the React component didn't acknowledge the event being fired because it wasn't the same as the onChange event the component was bound to.

It took a lot of trial and error. Lots of Ruby attempts with fill_in, send_keys, etc.

And there was a lot that didn't work at all. I knew you could evaluate javascript with Capybara, which led me into doing a lot of console testing in the browser (much more efficient than running a test every time I change something). It made it very easy to instantly test various hypotheses.

Here's what finally worked:

Researching React, we discovered the React Test Utilities package. In our parcel config (found in components.jsx — the file we tell parcel to use the command line) we were able to import it:

import ReactTestUtils from 'react-dom/test-utils';

Then at the bottom of the parcel config file, I exposed it to the browser:

window.ReactTestUtils = ReactTestUtils;

This made it available to the headless Chrome which is used in our system tests with Capybara and Selenium.

Finally, the tests were solved by writing the following code:

test 'valid value in input field' do
	# Set the value
	page.evaluate_javascript('document.getElementById("name_input_field").value = "Testing"')
  # The Magic Sauce: Tell React to simulate a change event
	page.evaluate_javascript('ReactTestUtils.Simulate.change(document.getElementById("name_input_field")')
	page.click_button "Save"
  assert_text("Testing")
end

And boom goes the dynamite! The lights are green and the system tests pass!

I wrote this because I had a hard time debugging this and thought someone else might run into the same issue as I did. I hope this helps someone out there, if not myself in the future when I run into this again! 😊