Cypress testing with Rails API Backend
Tags: rails, devops, cypress
Published: 2019-08-12 01:09:26

---
title:Cypress testing with Rails API Backend
published: true
description: 
tags: Rails, DevOps, Cypress
---

At one of our current projects we started testing our key functionalities with Cypress for our React front-end. As we started writing our tests we stumbled upon a problem, how could we have a clear and easy way to setup and clear the data created/modified in the test in a secure way?

We couldn´t do it directly in our API as our front end is fed by several APIs. We also wanted our tests not to be written in Ruby so the [cypress-on-rails gem](https://github.com/shakacode/cypress-on-rails) wasn't an option. 

Our main goal was to do this **without having to add unwanted functionality to our Rails API**.

Our first approach was to create a Rails controller that would only be enabled for Cypress testing. We chose to secure it via _environment_ variables and an authentication token.

This option had one big **pro**: it was very easy to create and easy to maintain, it was only a very big controller in the middle of our production code.

Sadly, there were a number of drawbacks:
- Code meant as test helper was in the middle of the production and business code.
- We had to be really careful about the controller's security. Having a controller able to delete cascade several of our entities was a huge risk to have.

The last point was really important to us. A mistake in the APIs deployment could lead to someone wiping half of our database!.

That's why we looked into another option: _using Rails engines_.

The pros:
- Active Record models from our main app can be referenced from the engine.
- Endpoints used by Cypress won't be available in production.
- The code of the endpoints **won't even be available in the production bundle**.

This really lowered the chances that a mistake in deployment configuration left the endpoints available.

## The code

Lets create a new Rails project first:

```bash
$ rails new cypress_api --api
```

Inside our newly created API, lets create our Engine to declare our cypress-specific endpoints:

```bash
$ rails plugin new cypress --mountable
```

This will create a cypress folder at the root level with our new Rails engine and will also append the gem configuration in our main Gemfile. 

Run _bundle install_ and remove the TODOs from the engines gemspec to be able to start the app.

Now, lets create a new controller inside the main app to signal the API is active. Write this code into app/controllers/status_controller.rb:

```ruby
class StatusController < ActionController::API

  def status
    render json: {"status": "OK"}
  end
end
```

Let's do the same inside the engine, but with a different message. Inside cypress/app/controllers/cypress/status_controller.rb:

```ruby
module Cypress
  class StatusController < ActionController::API

    def status
      render json: {"status": "Cypress OK"}
    end
  end
end
```

Also, add the corresponding route config in config/routes.rb and cypress/config/routes.rb as well.

Last, we have to mount our engine's endpoints to our main app. The thing is, we can't do that statically as we don't want the endpoints to be available at all times. We have to do it _dynamically_.

To do this, we have to add this snippet to the Engines main class in cypress/lib/cypress/engine.rb:

```ruby
  config.cypress_engine = ActiveSupport::OrderedOptions.new
  initializer 'cypress_engine.configuration' do |app|
    if app.config.cypress_engine[:mounted_path]
      app.routes.append do
        mount Cypress::Engine => app.config.cypress_engine[:mounted_path]
      end
    end
  end
```

So what does this snippet do? If the _config.cypress_engine_ option is declared anywhere in our environment files or application file the engines endpoints will be appended to the main app with a prefix.

For now, let's declare that option in our development.rb file.

```ruby
 config.cypress_engine.mounted_path = "/cypress"
```

We are now declaring that our Cypress Engine endpoints will be mounted into our main app with the _cypress_ prefix.

Let's try it out! Start Rails in development mode and access _"/status"_ and _"cypress/status"_. We can see that both endpoints are available!.

Now, lets start the app in production mode. If we try to access _"/cypress/status"_ we can see that 404 is returned.

Great! Now the Cypress endpoints are not available in the production environment but is still bundled into our app. To omit adding the endpoints to our app while in production we can limit the Gem to only be installed while in development.

```ruby
  group :cypress do
    gem "cypress", path: "cypress"
  end
```

We can also add another level of security to our engine in case someone removes this config by mistake. 

Add this snippet to cypress/lib/cypress/engine.rb below what we previously added:

```ruby
  initializer "cypress_engine.cypress_only" do
    unless Rails.env.development?
      abort <<-END.strip_heredoc
        Cypress Engine is activated in the #{Rails.env} environment. This is
        usually a mistake. To ensure it's only activated in cypress
        mode, move it to the development group of your Gemfile:
    
            gem 'cypress', group: :development
      END
    end
  end
```

If the cypress engine is used in an environment other than development, Rails startup will be aborted.

You can find all the code in this repo:
{% github tomsfernandez/Rails-Cypress-API-Example %}

(In the repo a new environment called "cypress" exists to add another level of security but it isn't really necessary)