Creating a tested React app

In this post, I’m going to step through getting a basic starter project going in ReactJS.

TL;DR

$ git clone https://github.com/bsdavidson/react-starter

Creating a React app from scratch, with tests

You can see where we will end up on my Github repo for the tutorial.

This React starter is my own opinion of a basic starting environment for creating React apps. Facebook recommends using their utility create-react-app. While nice to have something that gets all the boring setup out of the way, it is still a good idea to go through the process so you understand what things are doing and how they work in case you need to make your own modifications.

Prerequisites

Familiarity with JavaScript and the command line will be useful. You will also want to have NodeJS installed

Initial Setup

To start, we need to create a folder where everything lives

$ mkdir react-app
$ cd react-app

Now time to create a few more files and folders. For the purposes of this demo, let’s assume that we’ll need a stylesheet and some static assets like images.

$ mkdir assets
$ touch styles.scss
$ touch index.html

Notice we haven’t added a .js file yet, since our JavaScript code will live in the components we create later.

The index.html will be our initial starting point when loading our page. It won’t contain much content, but will act as a place to bootstrap the rest of our React app.

Now it’s time to create the files and folders where the business end of our app lives. I like to keep the source files and test files isolated in their own folders so we don’t clutter things up. Let’s go ahead and create some folders and stub files, to see how that will look.

$ mkdir src
$ mkdir test
$ touch src/app.jsx
$ touch test/test_app.jsx
$ touch style.scss

Ok, so what have we got here? Looks like we have a src folder for the app logic and a test folder for tests. Not bad so far. Also, since we are going to do test driven development (TDD) we will need to have our test layout setup at the same time.

At this point, we need to get all our supporting cast members installed. Because this is JavaScript, you have a lot of choices for these components and the combinations are pretty varied. In this tutorial, I have chosen a combination that I think works really well and which I actually use in my own apps. I won’t go into detail the merits of each one, but here is a list of what we will be using here along with their relevant links.

Components for writing our code:

  • Webpack: Collects and manages all our code so it can be easily packaged for distribution.
  • Sass: For writing our stylesheets.

For writing our tests:

  • Karma: Runs our tests in a real or headless browser.
  • Mocha for our test framework.
  • Chaijs as the assertion library
  • Enzyme testing utilities (instead of the built in react-test-utils). Makes writing tests far less painful.

Setup NPM and friends

First thing we need to do is initialize our NPM package. This creates a package.json file as well as a node_modules folder where all our dependencies (mentioned above) live. The package.json file enumerates the packages your app uses.

To initialize NPM:

$ npm init

It will ask you a number of questions. Here is what I put:

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See npm help json for definitive documentation on these fields
and exactly what they do.

Use npm install <pkg> --save afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (reactTut) react-app
version: (1.0.0)
description: Awesome Hello World app!
entry point: (index.js) src/app.jsx
test command:
git repository:
keywords:
author: Brian Davidson
license: (ISC)

Just answer them the best you can, and skip the ones you can’t. Don’t worry, we can edit them all later in package.json but make note of the entry point. Notice I put the path to our newly created src/app.jsx file.

If you look inside the package.json now, it’s pretty basic and doesn’t contain any information about our apps dependencies. That’s because we haven’t told it about them yet.

When installing dependencies, you need to know if something is used for just for development or will it also be needed for production as well?

In this starter, we won’t have any production dependencies because webpack will take everything and package it up for our final distribution copy. This means that when we run npm run dist we will have a fully independent page that can be dropped into any web server.

To get started, lets install our first dev-dependency.

$ npm install --save-dev react

Awesome. If you look at your package.json, you’ll have a new section called devDependencies with one item:

...
"devDependencies": {
"react": "^15.1.0"
},
...

The --save-dev option in the command we entered automatically added the entry in the file.

At this point, we have a devDependencies section in our package.json file for defining what is going into our app, but it is not yet complete. Let’s go ahead and install the rest of our dependencies now.

$ npm i --save-dev babel babel-core babel-loader babel-preset-es2015 babel-preset-react chai css-loader enzyme eslint eslint-plugin-react extract-text-webpack-plugin file-loader html-loader install json-loader karma karma-chrome-launcher karma-coverage karma-mocha karma-mocha-reporter karma-notify-reporter karma-sourcemap-loader karma-webpack mocha node-sass npm react react-addons-test-utils react-dom sass-loader style-loader url-loader webpack webpack-dev-server

After all that is done, you should have an entry in your package.json file that looks like this:

"devDependencies": {
    "babel": "^6.5.2",
    "babel-core": "^6.10.4",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.11.1",
    "chai": "^3.5.0",
    "css-loader": "^0.23.1",
    "enzyme": "^2.3.0",
    "eslint": "^2.13.1",
    "eslint-plugin-react": "^5.2.2",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "html-loader": "^0.4.3",
    "install": "^0.8.1",
    "json-loader": "^0.5.4",
    "karma": "^1.1.0",
    "karma-chrome-launcher": "^1.0.1",
    "karma-coverage": "^1.0.0",
    "karma-mocha": "^1.1.1",
    "karma-mocha-reporter": "^2.0.4",
    "karma-notify-reporter": "^1.0.0",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-webpack": "^1.7.0",
    "mocha": "^2.5.3",
    "node-sass": "^3.8.0",
    "npm": "^3.10.2",
    "react": "^15.1.0",
    "react-addons-test-utils": "^15.1.0",
    "react-dom": "^15.1.0",
    "sass-loader": "^4.0.0",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  }

One more package.json section we are going to edit is scripts. Scripts will help us by making our commonly run commands easier to type. Find the scripts section in package.json and replace it with what I have below:

  "scripts": {
    "build": "webpack",
    "dist": "npm run build && rm -rf dist && mkdir dist && cp -Rv index.html assets bundle.css bundle.js dist/",
    "start": "webpack-dev-server",
    "test": "karma start karma.config.js --single-run",
    "test-watch": "karma start karma.config.js"
  }

For each of the sections listed above, you will be able to run them using npm run xxxx. For example, to start our development web-server, you just need to type:

$ npm run start

These won’t do anything just yet, so, let’s continue…

Now that our app has access to all the modules we need to run and test our Hello World React app, we need to create a couple more configuration files because these modules need some direction in what to do.

Configuring Webpack

The first of these modules is Webpack. Webpack does a number of things, but at its basic heart is its ability to take all our JavaScript/CSS dependencies and “bundle” them up into a couple of files that we can distribute with our awesome Hello World app. In order to do this, it needs a webpack.config.js file defined that tell it how to operate.

Since there isn’t a webpack init to create our config file, let’s just create one.

$ touch webpack.config.js

In this file, paste in the following:

// webpack.config.js
var webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  devtool: "source-map",
  entry: {
    "index": "./src/app",
    "css": "./style.scss"
  },
  externals: {
    "react/lib/ExecutionEnvironment": true,
    "react/lib/ReactContext": true,
    "assets": true
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loader: "babel-loader",
        exclude: /node_modules/,
        query: {
          presets: ["es2015", "react"]
        }
      },
      {
        test: /\.s?css$/,
        loader: ExtractTextPlugin.extract("style", "css?sourceMap", "sass?sourceMap")
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("bundle.css")
  ],
  output: {
    path: __dirname,
    filename: "./bundle.js"
  },
  resolve: {
    extensions: ["", ".js", ".jsx"]
  }
};

So, let’s go over this. First off, it’s a JavaScript file. This means that you can put regular ol’ JavaScript in there and it will work. That might not seem useful right now, but keep it in mind anyway as you will use JavaScript syntax when editing.

var webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");

First line loads the webpack module, and the second line adds in an optional plugin that we need to bundle our CSS. If you didn’t need the CSS bundling feature, you could leave this line out.

devtool: "source-map",

This is also optional. This defines a module that is used to enhance debugging of webpack. More Info

entry: {
"index": "./src/app",
"css": "./style.scss"
},

This defines the main entry points into our app. The first line index tells webpack to start looking for dependencies from the app.js* file. Any files that app.js requires will also be included as well as any files that those require and so on. “CSS” is the same as app, but for CSS :-).

externals: {
"react/lib/ExecutionEnvironment": true,
"react/lib/ReactContext": true,
"assets": true
},

Externals defines things that you want webpack to ignore completely. Normally, webpack will work its way through your modules and bundle up all the dependencies for each module as well as their dependencies, and their dependencies and so on. Defining externals tells webpack to not bundle those libraries/resources into our bundle.js file. In our example, we are ignoring a couple of react modules and our assets folder. The React lines are there because of Enzyme. Enzyme has backwards compatibility with older versions of React and because of that, it references things that don’t exist in the new version we are using. Webpack doesn’t know that and will try and bundle the resouces that don’t exist.. producing an error. By marking them as externals, we prevent that.

The assets are there because we will just reference those files directly in our code and don’t want them getting pulled into our bundle.js file and bloating it all up.

module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loader: "babel-loader",
        exclude: /node_modules/,
        query: {
          presets: ["es2015", "react"]
        }
      },
      {
        test: /\.s?css$/,
        loader: ExtractTextPlugin.extract("style", "css?sourceMap", "sass?sourceMap")
      }
    ]
  },

The module section defines special tools that webpack uses to transform our modern ES6/Sass/JSX code into old fashioned ES5 and CSS. It does this through loaders, which is an array of objects that define what each loader is. I’ll break it down a little further:

test defines the file pattern that webpack will be looking for when applying this loader. In our file, it will be looking for any file that ends in .js or .jsx to run through the loader.

loader: "babel-loader" tells webpack that it will use be using Babel to translate our ES6 and JSX code before putting it into our bundle.

exclude: defines folders/files that the loader will ignore. Since we don’t need Babel to transpile anything in our node_modules folder, we have added it here.

query: defines additional settings used by the loader. In this case, Babel needs to be told what type of code it will be transpiling. For us, this means React and ES2015 Code and Babel uses presets to define these. Learn more about presets here.

The second loader we are using is optional. We are telling webpack to take our .CSS/SCSS files and extract them into a seperate CSS file instead of in-lining it into our JavaScript. It does this through the ExtractTextPlugin which you can read more about here.

For the final section..

 plugins: [
    new ExtractTextPlugin("bundle.css")
  ],
  output: {
    path: __dirname,
    filename: "./bundle.js"
  },
  resolve: {
    extensions: ["", ".js", ".jsx"]
  }

plugins: defines how our plugins are called. In this case, we are calling our ExtractTextPlugin and giving it a parameter of the output filename it will be creating from our SCSS and CSS files.

output: defines our destination for the resulting webpack bundle. We are telling it that its starting path is in the root of our app and to give it the name bundle.js.

resolve: defines how and what webpack will look for when finding files to include in our bundle. In this case, we are telling webpack to look for .jsx and .js files.

That’s it for webpack config. Now just one more config file to create before we are ready to start making our app.

Configuring Karma

Karma is our test runner and is where all of our tests will be loaded and evaluated. It does this by taking our code and running it in browser (in this case, Chrome), so that our testing framework can evaluate the output. Like Webpack, Karma is modular and can make use of a number of testing frameworks, browser configurations, preprocessors, etc. At this point, we need a configuration file to tell Karma what it will be using to do its job. We don’t have one yet, so lets create that now.

$ touch karma.config.js

Go ahead and copy the config text below and paste into this file. I’ll go over what all this does afterwards.

// karma.config.js
var webpack = require("webpack");

module.exports = function(config) {
  config.set({
    frameworks: ["mocha"],
    files: ["test/test_app.jsx"],
    preprocessors: {
      "test/index.js": ["webpack", "sourcemap"]
    },
    reporters: ["mocha"],
    mochaReporter: {
      showDiff: true
    },
    browsers: ["Chrome"],
    webpack: {
      devtool: "inline-source-map",
      externals: {
        "react/addons": true,
        "react/lib/ExecutionEnvironment": true,
        "react/lib/ReactContext": true
      },
      module: {
        loaders: [
          {
            test: /\.json$/,
            loader: "json"
          },
          {
            test: /\.jsx?$/,
            loader: "babel-loader",
            exclude: /node_modules/,
            query: {
              presets: ["es2015", "react"]
            }
          }
        ]
      },
      resolve: {
        extensions: ["", ".js", ".jsx", ".json"]
      },
      watch: true
    },
    webpackServer: {
      noInfo: true
    },
    singleRun: false
  });
};

Lets start with the first declaration:

var webpack = require("webpack");

In order for Karma to do it’s thing, it needs all our modern fancypants ES6 and JSX code transpiled (just like any other browser would), so to do that, it has to run our code through all the same steps that we are doing with webpack before it can run our tests.

frameworks: We are telling Karma to use Mocha as our framework of choice. Other choices include Jasmine and Quint.

files: this defines our apps starting point.

preprocessors: Here we tell Karma what it needs to do before it can load up our app. We are telling Karma that it will create a sourcemap of our app and compile it using webpack. A sourcemap is an additional file that makes debugging easier by showing errors as they relate to our source file and not from the compiled version.

reporters: This defines what Karma will use to output to the console the results of our tests. I’m using Mocha’s reporter (because it’s clear and concise).

mochaReporter: showDiff: true An additional option for our reporter. This will give a GitHub-like diff of any test failures. This will be VERY useful later when testing the equality of JSON, arrays, objects, or any large blob of text.

browsers: Karma supports a number of browsers out of the box. We are just using Chrome at the moment. You can add additional browsers to make sure your tests pass in different environments. More about Karma browser support.

webpack: Hey, it looks like our webpack.config.js file! Because Karma doesn’t care about some of the things we need or use in webpack for our users, it has it’s own copy of the webpack config. In this case, you’ll notice there is nothing in there for CSS processing since Karma or our tests don’t look at styling.

webpackServer: We are telling the webpack webserver not to output to our console, since we want to pay attention to the messages coming from our testing framework.

singleRun: false We are telling Karma to stay running and watch our files for changes. Anytime a change occurs, it will re-run the tests.

That wraps up our Karma config, lets make an app!

Index.html and style.scss

Lets start by filling in our empty index.html and style.scss file.

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1.0, width=device-width">
  <link rel="stylesheet" type="text/css" href="bundle.css">
  <title>Hello World</title>
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>

Pretty straight forward, but I’d like to point out a few things.

...
<link rel="stylesheet" type="text/css" href="bundle.css">
...
<div id="app"></div>
<script src="bundle.js"></script>

The important part is the empty div. This is where React will load our app. The bundle.css and bundle.js files don’t exist yet, but will after Webpack runs the first time.

What we have so far..

So lets take a quick break to verify what we have so far. You should have a directory structure that looks like this:

App
│   index.html
|   karma.config.js
|   package.json
│   style.scss
│   webpack.config.js
└───assets
└───dist
└───node_modules
└───src
    │   app.jsx
└───test
    │   test_app.jsx

Hopefully yours looks like what you see above. If not, the just go back and fill in what you may have missed.

Tests first!

Lets do this right by writing a test for our yet to be made app. Open up test/test_app.jsx.

For next part, don’t copy and paste. Type it all out. It’s not much and it will help you to remember.

We will start by importing our dependencies.

import React from "react";
import { assert } from "chai";
import { shallow } from "enzyme";
import { App } from "../src/app";

We will be needing React of course, so that comes first. Followed by assert (from Chai) and shallow (from Enzyme). Assert is what we will be using to actually test a condition. Enzyme is a helper and gives us a much cleaner and easier way of writing our tests. Last is our app which needs to be loaded so we can test it.

Next, let’s write a test. We will do this one line at a time.

describe("App", function() {

Here we are simply telling Mocha that we are starting a new group of tests. “App” could literally be anything that describes what we are testing.

  it("should render Hello World!", function() {

Here we define a test case. We are stating our expected result.

    let wrapper = shallow(<App />);

Here we are using Enzyme to generate a virtual version of our app (which doesn’t exist yet). Shallow means that it won’t build any child components so your tests are isolated to a specific component. We don’t have to worry too much about that at this point, but I like to use shallow where I can.

The alternative to shallow is mount and render which will fully render the app and usually work anytime shallow doesn’t. Medium has a decent write up here.

    assert.equal(wrapper.find("h1").text(), "Hello World!");

We are using assert.equal to test for equality. It takes two required parameters and one optional one. The first param is our actual result. In this case, we are using the find method defined on our wrapper (thanks to Enzyme) to look for an H1 tag and examine the contents. Whatever it happens to find there will be passed into the assert.equal method.

The second parameter is what we expect it to find. If it doesn’t match that, then the test will fail. There is an optional third parameter which is simply a text string that will be printed out in the case of a test failure. We aren’t using that here.

Close it out:

  });
});

Go ahead and save the file. You should have something that looks like the following:

import React from "react";
import { assert } from "chai";
import { shallow } from "enzyme";
import { App } from "../src/app";

describe("App", function() {
  it("Should render Hello World!", function() {
    let wrapper = shallow(<App />);
    assert.equal(wrapper.find("h1").text(), "Hello World!");
  });
});

At this point, if you run a test:

$ npm run test

You should get something like this:

...
START:
  App
    ✖ Should render Hello World!

Finished in 0.026 secs / 0.006 secs

SUMMARY:
✔ 0 tests completed
✖ 1 test failed

FAILED TESTS:
  App
    ✖ Should render Hello World!
      Chrome 51.0.2704 (Mac OS X 10.11.5)
    TypeError: Cannot read property 'propTypes' of undefined
...

Awww, it failed. Now, let’s make it pass! For that, we need to make our app.

Building Hello World

Open up src/app.jsx

Like our tests, let’s do this line by line.

First, let’s import React.

import React from "react";

Now, we need to create a React class that will define our app.

export let App = React.createClass({

Here we are creating a component class. An app can consist of one or hundreds of components. The normal structure of a React app starts with a root component that contains many child components. Our app will have just one, the root.

  render: function() {

Every component needs a render function to tell React what it will be outputting.

    return (
      <div>
        <img src="../assets/react.png" />
        <h1>Hello World!</h1>
      </div>
      );

A render function must be defined as a return. You will notice that our render is just plain HTML. If we weren’t using JSX, this section would look a lot different and much more verbose. It’s worth reading about raw React to get a better understanding of what is going on under the hood.

ReactDOM.render(<App />, document.getElementById("app"));

Here we are finally rendering our app to the page. ReactDOM.render takes two arguments. the first defines our component, the second defines where it will end up in our HTML.

Now let’s close it out.

  }
});

At this point, we should have a file that looks like this:

import React from "react";

export let App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Hello World!</h1>
      </div>
      );
  }
});

ReactDOM.render(<App />, document.getElementById("app"));

Go ahead and save the file. You should now be able to start our webpack server and see our creation!

$ npm run start

You will see webpack build your files and start a webserver. Go ahead and open up a web browser and put in this URL http://localhost:8080/webpack-dev-server/.

It’s pretty good, but kind of ugly. Lets fix that. Leave the browser open, open up style.scss and paste this in.

h1 {
    color: #0066ff;
}

Go ahead and save it and take a look at your still open browser window. Pretty nice right!? The webpack server will auto update your page with any changes you make to the app or stylesheets. Ok, let’s see if our tests pass! Press Control+C to kill our webpack server.

Run our test:

$ npm run test

Test failed?!! Lets see why:

Uncaught Invariant Violation: _registerComponent(...): Target container is not
a DOM element.

WAT? Ok, there is a good reason for this. In our app.jsx file we are calling the render function, but it’s trying to render to an element that doesn’t exist when the file is included. To fix this, we need to separate our component from our render so it can be properly tested.

To do this, we need to perform a little refactoring.

Refactoring for tests

Each of our components should be able to be loaded in isolation for easier testing. This means we don’t want our app component to be concerned also with rendering it out to the page.

Lets start by adding two files that will serve as our new entry points into our app.

$ touch src/index.jsx
$ touch test/index.js

Open up src/index.jsx.

Lets copy some of what we have already written in app.jsx over to here.

<!-- src/index.jsx -->
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./app"

ReactDOM.render(<App />, document.getElementById("app"));

The only new thing here is that we are importing our app into index.jsx. Go ahead and save it.

Since we are rendering from here, you can now DELETE the following lines from app.jsx

import ReactDOM from "react-dom";

and

ReactDOM.render(<App />, document.getElementById("app"));

Let’s now open up test/index.jsx and ADD the following:

import "./test_app";

Make sure you’ve saved all your files at this point. We are almost done. All we need to do is tell Node, Webpack and Karma about the new changes. First, open package.json and update the main: entry to the following

"main": "src/index.jsx",

Then open karma.config.js update the files: entry to:

files: ["test/index.js"],

And one more, open webpack.config.js and change entry: to read:

entry: {
    "index": "./src/index",
    "css": "./style.scss"
  },

These changes tell the respective apps where it will look to bootstrap our app.

Go ahead and run npm start and verify that our app still loads. If not, we need to go back and verify all the changes have been made and saved.

And now, for the moment of truth: run npm run test and see if it passes. Hopefully you should have seen all green. If not, check out the errors and see if you can see what went wrong.

Compile and ship it!

Finally, lets package our app and get it ready for production. Type:

$ npm run dist

After a few seconds, webpack will have bundled your app and a couple of bash commands will have copied it to the dist/ folder. This is your compiled app, ready for the browser.

Ok, that’s it. I hope you found this helpful. If you have any suggestions for the starter app, send me a pull request or open an issue. Remember, you can download a fully functional starter from my GitHub repo

For more information on getting started with React, I highly recommend Learn Raw React, by James Nelson as well as his follow up tutorials.