This post was updated on 4 August 2023 to remove some outdated content and replace it with a new 'Running a Basic System For the First Time' section.
System tests are meant to auto-test the way users interact with your application, including the Javascript in your user interface. Minitest, being the default testing framework in Rails, is a great match for system testing. It is perfect for getting started with system testing Rails apps as it's small and fast, giving you the ability to write clean and readable tests.
In today's article, we'll look into using Minitest to run some simple system tests in a Rails 7 application. By the end of the tutorial, you should know what Minitest is and how to start using it in your application.
Let's get started!
Introduction to System Tests
In Rails jargon, system testing refers to "testing an application as a whole system". Instead of testing separate parts, with system tests, we can test a whole 'workflow', just like a user interacting with our app. In a nutshell, system tests are automated "A-to-Z" tests in Ruby on Rails apps. On the other hand, they are different from integration tests: integration tests are for testing behavior, especially of every part of an app together, but not via the user interface.
Now that we know what system tests are, let's continue with the example app we'll be using today.
Generating a New Rails App
Run the command below to generate a very basic Ruby on Rails 7 app:
Once that is done, let's see what we need to begin running some system tests.
Configuration
Go ahead and open up the project in your favorite editor and proceed to the Gemfile
, specifically to the test
block:
The gems you see listed there are the basic tooling we need to run some system tests in our app. Now, let's see what each gem does:
- Capybara - To begin with, the Capybara gem is used for interacting with the browser. It's through Capybara that we can make the tests visit pages, fill in forms, click on links and buttons. If you've worked with Capybara before, you're aware of all the things you have to coordinate to make it work with a database cleaning strategy and the browser configuration. Hooray for the Rails system test set up: it takes care of all the configuration that is needed to work with Capybara out-of-the-box, so we can focus on the actual test writing.
- Selenium webdriver - Selenium webdriver is a very popular gem that is used to interact with the browser and works by simulating real-user interactions.
- Webdrivers - This gem works hand-in-hand with Selenium webdriver by providing "hooks" into almost all of the modern browsers available today.
Next, let's do a quick scaffold generation to have something to work with:
Usually, generating a scaffold will automatically generate the application_system_test_case.rb
and everything you need for the system tests:
When Eileen Uchitelle introduced system tests, Chrome was chosen over Firefox because at the time Firefox didn't play nicely with Selenium. That was fixed in later versions of Firefox, so I replace Chrome with Firefox, like so:
The other important thing to note is that the Selenium driver is different from Capybara's default and is chosen because it works with Javascript. And this is all you need to run basic system tests.
Running a Basic System For the First Time
We can quickly do a quick system test from our basic app to get a feel of what they are all about by running the command below:
If everything goes as expected, you should see a browser window automatically open and the a complete simulation of a user creating a blog post (using our example scaffold) being done by the system test suite. After it completes, you'll get a summary like the one below in the terminal in your app's root:
Quick tip: If you hadn't run any database migrations when you run this command, you might get an error telling you to run the migrations first, go ahead and do so then re-run it.
Choosing Between a Real Browser and Headless Browser
I can't get enough of watching the system tests running in the browser and see how all the links are clicked, forms are filled in, etc. But, it is slow. To speed up the test run, you can use a 'headless' browser: that is a browser that has the same access to your app as a regular browser, but without the graphical user interface. Meaning: it works the same but you don't actually see the tests doing their work since a headless driver doesn't open an actual browser window.
If you do want to go headless, there's headless_chrome
and headless_firefox
. To use them, there's one small change needed:
After making this change, you can re-run the basic rails test:system
test we did earlier, this time using the headless Firefox driver. See if you get results similar to mine below:
One thing you'll note with running headful versus headless is that the headless version is a bit faster compared to the headful version. If you are running lots of system tests, headless is the way to go since it will save you some time. But if watching the automated browser interacting with the default headful version is your thing, go ahead and run the default headful version.
There's more customization you can do, but to get started, this is really all you need.
Running the Tests
$ rails test
will run all the tests except the system tests. You need to explicitly run $ rails test:system
. (Fun fact: the $ rails
command will always run through bin/rails. No need to type $ bin/rails
anymore.)
- Run all the system tests:
$ rails test:system
- Run test in a specific file (here: users_test.rb)
$ rails test/system/users_test
- ... or one specific test:
$ rails test test/system/users_test.rb:21
- To run all the tests: run the system tests first:
$ rails test:system test
Note: the options flags don't work with test:system
; if you want to use flags like -f
(for fail fast) or -v
(for verbose), use $ rails test test/system -v -f
What (Not) to Test
System tests are complementary to unit tests, not a substitute. It will do to test a happy path, plus maybe one path with an error message or redirect. System tests are not meant to test all the edge cases in the browser; just cover the main features.
When choosing my test subject, I try to find an entity to test that reflects how the user uses the app. For the naming of my tests, I borrowed GitLab's naming convention of ROLE_ACTION_test.rb, which fits this approach well. For instance: user_shares_card_test.rb
.
General Tips and Tricks
- If you use Devise for authentication, you can use the Devise integration helpers to log in and log out test users. Add
include Devise::Test::IntegrationHelpers
to a test class, or add it in thetest_helper.rb
file to make them available in all tests. Now you can create a user andsign_in(@user)
. If you put that in asetup
method, you don’t need to log out the user in a teardown. Rails takes care of cleaning up what's in the setup method. - Working with forms, it's tempting to reference the ids that Rails creates automatically from model name + field name and the button types for
click_on
s. (fill_in "user_email"
,click_on :commit
). But since systems tests are about what a user would actually see on their screen, it makes sense to use visible elements, i.e. texts. A reasonable option would be to have reference keys in i18n locale files and use those keys instead of the ever-changing literal texts. (fill_in :user_email
). Capybara finds the text only with the full I18n syntax:assert_selector "h1", text: I18n.t("activerecord.models.things")
. - The path helpers are included by default. For some libraries you need to include the helpers in the test class, for instance
include ActionMailer::TestHelper
, to use theassert_emails
method. - Create custom classes to run the tests on different screen sizes. Check out the guides.
- The screenshot feature can also be used to take screenshots for your documentation and promotional materials. If you make it a screencast and slow down the playback speed, you have a product video in minutes!
- Rails provides a generator for system tests.
- Minitest in Rails is slightly different than Minitest itself, and also adds Rails specific methods and assertions. Check the Rails docs first before the Minitest docs.
I had good fun with the system tests, and that was, for the most part, thanks to how easy it was to use it out-of-the-box.
Conclusion
In this post, we looked at what configuration Rails delivers out-of-the-box and what minimal customizations we may want to add. Minitest is great for writing System tests. A few tips and tricks should help to get your first system tests up and running. Watch them do their magic in the browser!