Skip to content

Instantly share code, notes, and snippets.

@lmiller1990 lmiller1990/blog.md Secret
Created Sep 28, 2018

Embed
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 cypress.io 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.

https://gist.github.com/61a9f1a74181246c10b08fe23e234c35

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

https://gist.github.com/d93603b557e962332a16f1717e6bde5d

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:

https://gist.github.com/e8146c71b1c6301587f812a9d0dfe2e0

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):

https://gist.github.com/50a67657ad4d504a68b6011d2ec9524b

Update config/routes.rb:

https://gist.github.com/1aa5f729579bd3fb5bb76001de698d9b

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

https://gist.github.com/75ae99489a916e39bf2cd7642c4b636e

Create some views with

https://gist.github.com/15751d01e8878ac89d597b1e9a0f2058

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

https://gist.github.com/8989ab40a2f48aecdfa3bc1a16d27075

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:

https://gist.github.com/ffbfb83863cbfdd909fd3161d743a3a3

This fails with:

https://gist.github.com/4bf1bf06270ab8383388c4f7a5e70e7d

Update app/controllers/posts_controller.rb first:

https://gist.github.com/52d94c38ce7a4eacc189e9fbebf42bca

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

https://gist.github.com/027703a2b67eeb3879c3bde1bace764d

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:

https://gist.github.com/81c9eff23e5446f184c9b9909846e73a

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

https://gist.github.com/3e329f4a40edb15224eb4bcc6e40be77

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.

https://gist.github.com/19559b07a8b51c8f1041f4ef0afe9eda

Next, update spec/system/posts_spec.rb:

https://gist.github.com/ff00fb8c4f6a970d3a23229eb3dd4da2

This should pass, too.

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

https://gist.github.com/058a4ad3e9d02211d298b4ae142ca7ed

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

https://gist.github.com/84553230f9c78ebb91bc689a7714363f

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:

https://gist.github.com/64c6a9cd81fec3c0cc78c8345d8c2dd7

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

https://gist.github.com/c711d5f9fc0e2e70742094efe64fc6cc

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:

https://gist.github.com/197dd1feda55495078c63981f15eb0f1

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:

https://gist.github.com/a910402ad082433244eee58216f7d269

Next, in db/seeds.rb add:

https://gist.github.com/eb38b069333841f66321792ab2c6f813

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:

https://gist.github.com/a023d5db3459b0d517e59f911c4d0224

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:

https://gist.github.com/4067a8b2470f56e0bad96a055b1410c6

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:

https://gist.github.com/6785c2ebe503c113f28f78a086e9ad59

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:

https://gist.github.com/55a98acd5ac0f9e391d732e3ea70eb31

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:

https://gist.github.com/eb8f4e669ec9bf0c78d3b5b263d4d366

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:

https://gist.github.com/a4b87a375b194fc9941f190f3cf17a12

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

https://gist.github.com/c5209e72548f203bd06ddee51a676ef6

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:

https://gist.github.com/aa825cb54ab576850d12dbed8722e03c

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.