Skip to content

Instantly share code, notes, and snippets.


lmiller1990/ Secret

Created Sep 28, 2018
What would you like to do?

Traditionally, Rails gives us a full stack development framework including E2E tests with Selenium to develop websites. Let's see how to transition an app using Rails' built in system tests to using Cypress, a new E2E framework built on Node.js, targetting modern JavaScript heavy applications.

A common Rails stack looks like:

  • RSpec for the testing framework
  • FactoryBot for populating the database
  • DatabaseCleaner (or just ActiveRecord) for cleaning the database between tests
  • Selenium for driving the browser in E2E tests

Moving to Cypress (at least for the E2E tests), it now looks like:

  • Mocha/Chai combo for the testing framework
  • No good replacement for FactoryBot
  • Need to figure the database clearing/truncation out on our own
  • Cypress for the browser tests

At first glance, and based on my experience, the stack is a lot less "batteries included", which is what I like about Rails. I'm continuing to try new things out. This article will

  1. Set up the traditional stack, and make a simple CRUD app with a few simple E2E tests
  2. Move to the stack, while implementing the same tests
  3. Dicuss improvements and thoughts

I like each blog post to be independant, and include all the steps to recreate it. If you don't care about setting up the Rails app with RSpec etc, just grab the repo here and move to the second half.

Creating the Rails App

Note: If you want to skip to the section where I add Cypress, ctrl+f "Installing and Setting Up Cypress".

Generate the Rails app, skipping MiniTest and using Postgres for the database with rails new cypress_app -T --database=postgresql. Update group :development, :test in the Gemfile:

Add FactoryBot and RSpec and webpacker.

Then run bundle install, and generate the binstub and system folder by running:

Initalize the database with rails db:create. That should have set up RSpec, FactoryBot and installed the dependencies for system tests.

Creating the Crud App

We will make a simple blog app, that lets an anonymous user create a post, which has a title, body and category. We need a Post and Category model - create them with the following:

Next, we need a posts_controller to create posts. Create one with touch app/controllers/posts_controller.rb. We will come back to this in a moment.

Update models/category.rb to reflect the has_many relationship (a category can have many posts):

Update config/routes.rb:

Add some code to app/controllers/posts_controller.rb:

Create some views with

Create a test with touch spec/system/posts_spec.rb, and add:

Make sure everything is working by running rspec spec/system. If the test passes, everything is working correctly.

E2E with Rails' System Tests

Before moving on to using Cypress, let's make sure the code is working correctly using the built in system tests, which run using selenium_chrome_headless. Update spec/system/posts_spec.rb:

This fails with:

Update app/controllers/posts_controller.rb first:

Now we need the views. Start with app/views/posts/_form.html.erb:

We included a flash message validating the minimum length of a post - we will add this validation in a moment. First, update app/views/posts/new.html.erb:

And lastly, app/views/posts/show.html.erb:

Now running rspec spec/system should give us a passing test. Let's implement two more tests, starting with validating the length of a post title. Update app/models/post.rb.

Next, update spec/system/posts_spec.rb:

This should pass, too.

Finally, add the following to app/views/posts/index.html.erb:

This shows a list of posts at /posts. Lastly, a test in spec/system/posts_spec.rb:

Running rspec spec/system should yield three passing tests.

Installing and Setting Up Cypress

Now we have a boring, yet working and well tested Rails app. Let's proceed to add Cypress and migrate our test suite. Firstly, install Cypress and a few dependecies with:

Next, following their documentation, add a command to package.json. Mine package.json looks like this:

Finally, run yarn cypress:open. You should see:

Furthermore, a cypress folder was created for you.

A Creates Post Test

Let's migrate the first test - creating a post succesfully - to Cypress. First, start the rails server by running rails server in a separate terminal from Cypress. Next, create the test with touch cypress/integration/posts.spec.js, and add the following:

The Cypress DSL is fairly easy to read. Strictly speaking, {force: true} should not be necessary. Some of my tests were randomly failing without this, though, so I added it. I'll investigate this in more detail later.

If you still have the Cypress UI open, search for the test using the search box:

This fails, of course:

Because no categories exist. Before implementing a nice work around, just create one by dropping down into rails console and running Category.create!(name: 'ruby'). Now the test passes!

There are some problems:

  1. Running the tests in the development env is not good. We should use RAILS_ENV=test.
  2. Need a way to seed some data, like a category.
  3. Should clean the database between each test.

Let's get to work on the first two.

Test Seed Data and Running in RAILS_ENV=test

Let's set up some basic seed data for the tests to use. First, create a seeds folder containing a test.rb file by running mkdir db/seeds && touch db/seeds/test.rb. Inside, add:

Next, in db/seeds.rb add:

This will seed the correct seed file based on the current RAILS_ENV.

Cleaning the Database between Tests

Now we have a way to seed data, but no way to clean the database after each test. The way I've been handling this is by making a POST request to dedicated /test//clean_database endpoint before each test, as recommended by Cypress. Let's make that API. First, update config/routes.rb:

Next create the controller and spec: mkdir app/controllers/test && touch app/controllers/test/databases_controller.rb and mkdir spec/controllers && mkdir spec/controllers/test && touch spec/controllers/test/databases_controller_spec.rb.

Starting with databases_controller_spec.rb, add the following:

There are two functions this API provides. Both specs test for truncation. We also allow a should_seed parameter to be provided. If should_seed is true, then we repopulate the database using the data defined in db/seeds/test.rb.

The controller implementation is as follows:

This should yield two passing specs. Now, restart the Rails server with RAILS_ENV=test rails server. Now, we need a way to actually access the API from within Cypress. Inside of cypress/support/commands.js, add the following:

Cypress automatically loads all the helpers in commands.js for us.

Since Rails is running on port 3000, and Cypress is assigned an arbitrary port, we need to support CORS for the /test routes. Inside config/environments/test.rb, add the following:

This allows CORS for the test environment only. Restart the Rails server, and reopen the Cypress UI if you closed it. It should pass... alas, it does not.

If you look closely, only on the initial opening of the Cypress UI, the browser kind of "flickers" once. For some reason, this causes the beforeEach hook to be called twice, messing up the seed data. The post request contains the category id of the first seed run, however since the browser flickers and causes the data to be reseeded, the initial category id used in the test no longer exists!

Once you have the UI running, however, simply rerunning the test should be enough to pass. Typically I only open the UI once, and leave it open, so it is not a big deal locally. On CI, this is a huge problem though. I'm going to get in contact with the Cypress team and see if they have a work around.

One last thing I want to add is the ability to seed some data, depending on the test. For this, I'll use another test-env-only controller. Create it with touch app/controllers/test/seeds_controller.rb. Add a test with touch spec/controllers/test/seeds_controller_spec.rb. Add the following test:

This endpoint will simply seed a specified number posts. Now, the implementation in seeds_controller.rb:

This test should pass. Here are two more tests - one for the case where a post title is too short, and an error is displayed, and another for the /posts index page. This once will make use of the new /seed_posts route, so update commands.js:

Everything passes!

Conclusion and Thoughts

This was a very long article. We covered:

  • Setting up a traditional Rails app
  • Installing Cypress
  • Creating specific route for cleaning and seeding the database
  • Using Cypress hooks, such as beforeEach, and custom commands

Cypress is certainly a great tool, and a refreshing new angle on E2E testing. The lack of support for non Chromium based browsers, and of information on how to integrate it with various backends led to some challenges. However, I'm positive Cypress is going in a good direction and will continue to refine my workflow and integration with Rails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.