The steps included here detail the steps I followed to get a new React on Rails app set up, with a focus on testing JS components with Karma / Jasmine / Enzyme. A lot of this was liberally borrowed / modified from the Launch Academy curriculum, but there are some additional steps involved to get everything working with webpacker:
Unless otherwise specified, run the code in the terminal
rails new project-name
cd project-name
Add to your gemfile and run bundle
:
gem 'webpacker'
Install Webpacker Packages:
rake webpacker:install
rake webpacker:install:react
Install Karma, Jasmine, PhantomJS for running Tests:
yarn add karma --dev
yarn add karma-cli --dev
yarn add karma-jasmine --dev
yarn add jasmine-core --dev
yarn add phantomjs-prebuilt --dev
yarn add karma-phantomjs-launcher --dev
Create files:
// Source code goes here:
app/javascript/react/src
// Test code goes here:
app/javascript/react/test
// Create empty testHelper.js file, this will load your js test files:
touch app/javascript/react/test/testHelper.js
Initialize karma:
karma init
At the prompt, select jasmine
for framework, then no
to RequireJS
Tab to select PhantomJS
as an autoloaded browser.
Use app/javascript/react/test/testHelper.js
for the location of test files, skip the next question.
Select yes
to run tests on change
Make webpack play nice with Karma: See: https://github.com/webpack-contrib/karma-webpack for reference
yarn add karma-webpack --dev
And add to karma.conf.js
preprocessors: {
'app/javascript/react/test/testHelper.js': ['webpack']
},
...
// Put this section at the bottom of the config.set block to void load order issues
// Make sure to add a comma to the end of the previous line before this.
webpack: {
module: {
loaders: [
{
test: /\.jsx?/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
}
And add the following to your testHelper.js:
import 'babel-polyfill';
import React from 'react';
import { mount } from 'enzyme';
import jasmineEnzyme from 'jasmine-enzyme';
beforeEach(() => {
jasmineEnzyme();
})
// function to require all modules for a given context
let requireAll = requireContext => {
requireContext.keys().forEach(requireContext);
};
// require all js files except testHelper.js in the test folder
requireAll(require.context('./', true, /^((?!testHelper).)*\.jsx?$/));
// require all js files except main.js in the src folder
requireAll(require.context('../src/', true, /^((?!main).)*\.jsx?$/));
// output to the browser's console when the tests run
console.info(`TESTS RAN AT ${new Date().toLocaleTimeString()}`);
Add Enzyme for React Component tests: See: https://github.com/airbnb/enzyme/blob/master/docs/guides/webpack.md for reference
yarn add react-addons-test-utils --dev
yarn add enzyme --dev
yarn add react-test-renderer --dev
yarn add jasmine-enzyme --dev
Update the webpack part of karma.conf.js
webpack: {
module: {
loaders: [
{
test: /\.jsx?/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
externals: {
cheerio: 'window',
'react/addons': 'react',
'react/lib/ExecutionEnvironment': 'react',
'react/lib/ReactContext': 'react',
'react-addons-test-utils': 'react-dom',
}
}
Add your test files and run with karma start
(and pray):
Example Component:
app/javascript/react/src/App.js
import React from 'react';
const App = props => {
return(
<h1>Hello World</h1>
)
}
export default App
Example Test:
app/javascript/react/src/AppTest.js
import App from '../../src/App';
import React from 'react'
import { mount } from 'enzyme';
import jasmineEnzyme from 'jasmine-enzyme';
describe('A test for App', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<App />)
})
it('should pass', () => {
expect(wrapper.find('h1').text()).toEqual("Hello World")
})
})
Now, get rails set up to render a react app. Create an index file that will serve as the point of entry:
touch app/javascript/packs/index.js
With the following Content:
import React from 'react';
import ReactDOM from 'react-dom';
import YourAppName from '../react/src/YourAppName';
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(<YourAppName />, document.getElementById('app'));
})
Then create a static rails page, this will need:
- A route in
routes.rb
:root 'static_pages#index'
- A StaticPagesController with an index route
- A view template:
app/views/static_pages/index.html.erb
Put a link to the index.js in the view page (or the layout) with:
<%= javascript_pack_tag 'index.js' %>
And include a div
tag with id="app"
on the page where you want your app.
How to run your app now:
- In one tab run:
./bin/webpack-dev-server
- In another run:
rails s
Enable hot module replacement! In config/webpack/development.js
:
module.exports = merge(sharedConfig, {
...
plugins: [
new webpack.HotModuleReplacementPlugin() // Enable HMR
],
...
dev Server: {
hot: true,
...
}
Make it easier to start the dev-server with foreman and a Procfile:
gem install foreman // Note that the foreman docs explicitly instruct you not to put it in your gemfile
touch Procfile
Put this in your Procfile:
web: bundle exec rails s
webpacker: ./bin/webpack-dev-server
Now you can start webpack and rails with one command: foreman start
Note that you may need to visit a different port now (try localhost:5000
)
Another tiny addition:
If you want to be able to start the webpack-dev server with
yarn start
instead of./bin/webpack-dev-server
you can pop into package.json and add an object of scripts like so: