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