Finally! Easy JS testing in Rails with Jest

Finally! Easy JS testing in Rails with Jest

Rails 5.1 introduced support for modern JavaScript development workflows. Now your Rails projects can take advantage of the wider JS ecosystem. One of the benefits that brings is an easier way of doing unit testing for the JS in your Rails project.

Testing JS in Rails was possible before, but becomes complicated if you want to use ES6 and modules. One of the projects that I work on created the infrastructure to test ES6 with Jasmine, but it’s kind of complicated.

The following is how you would set up basic testing with jest and ES6.

Now you can setup a new Rails project with webpacker (or add the webpacker Gem to an existing project):

rails new --webpacker

Then use yarn to to add jest to your development dependencies:

yarn add --dev jest

Since we want to use ES6 for our JS, we’ll need to install babel and babel-jest:

yarn add --dev babel-preset-env
yarn add --dev babel-jest

Make a folder to store your tests:

mkdir app/javascript/tests

In the package.json at the root of your project you do need to add a bit of setup so that jest knows where to find your tests.

{
  "name": "myapp",
  "private": true,
  "dependencies": {
    "@rails/webpacker": "3.5"
  },
  "devDependencies": {
    "babel-jest": "^23.4.2",
    "jest": "^23.4.2",
    "webpack-dev-server": "2.11.2"
  },
  "jest": {
    "roots": [
      "<rootDir>/app/javascript/tests"
    ],
    "verbose": true,
    "testURL": "http://localhost",
    "moduleFileExtensions": [
      "js",
      "json",
      "vue"
    ],
    "moduleDirectories": [
      "node_modules",
      "<rootDir>/app/javascript"
    ],
    "moduleNameMapper": {
      "^@/(.*)$": "<rootDir>/$1"
    },
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest"
    }
  }, 
   "scripts": {
    "test": "jest --config package.json"
  }
}

In .babelrc:

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": "> 1%",
        "uglify": true
      },
      "useBuiltIns": true
    }]
  ],
  "plugins": [
    "syntax-dynamic-import",
    "transform-object-rest-spread",
    ["transform-class-properties", { "spec": true }],
  ],
  "env": {
    "test": {
      "presets": [
        ["env", { "targets": { "node": "current" }}]
      ]
    }
  }
}

You can write your test and the implementation after this initial setup. Simply import your class in the test and create the class in ../libs/.

import HtmlStripper from '../libs/HtmlStripper'

test('strips html tags from a string', () => {
  const htmlStripper = new HtmlStripper({
    html: '<p>test</p>'
  })
  expect(htmlStripper.stripHtml()).toEqual('test')
})
export default class HtmlStripper {
  constructor (options) {
    this.html = options.html
  }

  stripHtml () {
    const doc = new DOMParser().parseFromString(this.html, 'text/html')
    return doc.body.textContent || ''
  }
}

Then run yarn test. Your test should pass, and you can even run yarn test --coverage to get coverage information or yarn test --watch to run your tests continuously in the background.

You’ll notice that this class uses DOMParser which is provided by the browser. This will work in the test environment using JSDom