page_id | series | permalink | day | title | description | hero_image | imageUrl | dayDir | introBannerUrl | protectedPreview | protectedPreviewLength | protectedPreviewCta | date | imagesDir | includeFile |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
30-days-of-react/day-29 |
30-days-of-react |
day-29 |
29 |
An Introduction to Continuous Integration for React Apps |
Today we'll look through some continuous integration solutions available for us to run tests against as well as implement one to test our application in the cloud. |
/assets/images/series/30-days-of-react/headings/29.jpg |
/assets/images/series/30-days-of-react/headings/29.jpg |
29 |
/assets/images/series/30-days-of-react/headings/29_wide.jpg |
true |
800 |
partials/series/30-days-of-react/30-days-protected-preview.html |
Tue Nov 01 2016 21:33:02 GMT-0700 (PDT) |
/assets/images/series/30-days-of-react/day-29 |
./../_params.yaml |
We've deployed our application to the "cloud", now we want to make sure everything runs as we expect it. We've started a test suite, but now we want to make sure it passes completely before we deploy.
We could set a step-by-step procedure for a developer to follow to make sure we run our tests before we deploy manually, but sometimes this can get in the way of deployment, especially under high-pressure deadlines in the middle of the night. There are better methods.
The core idea is that we want to deploy our application only after all of our tests have run and passed (sometimes known as "going green"). There are many ways we can do this. Mentioned above, we can handle it through humans, but that can become tedious and we're pretty good at forgetting things... what was I saying again?
Let's look at some better ways. One of the ways we can handle it is through a deployment script that only succeeds if all of our tests pass. This is the easiest, but needs to be replicated across a team of developers.
Another method is to push our code to a continuous integration server whose only responsibility is to run our tests and deploy our application if and only if the tests pass.
Just like hosting services, we have many options for running continuous integration servers. The following is a short list of some of the popular CI servers available:
Let's look at a few ways of handling this process.
Without involving any extra servers, we can write a script to execute our tests before we deploy.
Let's create a script that actually does do the deployment process first. In our case, let's take the surge.sh
example from yesterday. Let's add one more script we'll call deploy.sh
in our scripts/
directory:
touch scripts/deploy.sh
chmod u+x scripts/deploy.sh
In here, let's add the surge deploy script (changing the names to your domain name, of course):
#!/usr/bin/env bash
surge -p build --domain hateful-impulse.surge.sh
Let's write the release script next. To execute it, let's add the script to the package.json
scripts
object:
{
// ...
"scripts": {
"start": "node ./scripts/start.js",
"build": "node ./scripts/build.js",
"release": "node ./scripts/release.js",
"test": "node ./scripts/test.js"
},
}
Now let's create the scripts/release.js
file. From the root directory in our terminal, let's execute the following command:
touch scripts/release.js
Inside this file, we'll want to run a few command-line scripts, first our build
step, then we'll want to run our tests, and finally we'll run the deploy script, if everything else succeeds first.
In a node file, we'll first set the NODE_ENV
to be test
for our build tooling. We'll also include a script to run a command from the command-line from within the node script and store all the output to an array.
process.env.NODE_ENV = "test";
process.env.CI = true;
var chalk = require("chalk");
const exec = require("child_process").exec;
var output = [];
function runCmd(cmd) {
return new Promise((resolve, reject) => {
const testProcess = exec(cmd, { stdio: [0, 1, 2] });
testProcess.stdout.on("data", msg => output.push(msg));
testProcess.stderr.on("data", msg => output.push(msg));
testProcess.on("close", code => (code === 0 ? resolve() : reject()));
});
}
When called, the runCmd()
function will return a promise that is resolved when the command exits successfully and will reject if there is an error.
Our release script will need to be able to do the following tasks:
- build
- test
- deploy
- report any errors
Mentally, we can think of this pipeline as:
build()
.then(runTests)
.then(deploy)
.catch(error);
Let's build these functions which will use our runCmd
function we wrote earlier:
function build() {
console.log(chalk.cyan("Building app"));
return runCmd("npm run build");
}
function runTests() {
console.log(chalk.cyan("Running tests..."));
return runCmd("npm test");
}
function deploy() {
console.log(chalk.green("Deploying..."));
return runCmd(`sh -c "${__dirname}/deploy.sh"`);
}
function error() {
console.log(chalk.red("There was an error"));
output.forEach(msg => process.stdout.write(msg));
}
build()
.then(runTests)
.then(deploy)
.catch(error);
With our scripts/release.js
file complete, let's execute our npm run release
command to make sure it deploys:
npm run release
With all our tests passing, our updated application will be deployed successfully!
If any of our tests fail, we'll get all the output of our command, including the failure errors. Let's update one of our tests to make them fail purposefully to test the script.
I'll update the src/components/Nav/__tests__/Navbar-test.js
file to change the first test to fail:
// ...
it("wraps content in a div with .navbar class", () => {
wrapper = shallow(<Navbar />);
expect(wrapper.find(".navbars").length).toEqual(1);
});
Let's rerun the release
script and watch it fail and not run the deploy script:
npm run release
As we see, we'll get the output of the failing test in our logs, so we can fix the bug and then rerelease our application again by running the npm run release
script again.
Travis ci is a hosted continuous integration environment and is pretty easy to set up. Since we've pushed our container to github, let's continue down this track and set up travis with our github account.
Head to travis-ci.org and sign up there.
Once you're signed up, click on the +
button and find your repository:
From the project screen, click on the big 'activate repo' button.
To allow Travis CI to automatically log in for us during deployment, we need to add SURGE_LOGIN
and SURGE_TOKEN
environment variables. Open the More Options menu and click settings.
Under environment variables, create a variable called SURGE_LOGIN
and set it to the email address you use with Surge. Next, add another variable called SURGE_TOKEN
and set it to your Surge token.
You can view your surge token by typing
surge token
in your terminal. Since we're usingsurge
for depolyment, we should alsoadd it to ourdevDependencies
inpackage.json
. Runnpm install surge --save-dev
to add it
Now we need to configure travis to do what we want, which is run our test scripts and then deploy our app. To configure travis, we'll need to create a .travis.yml
file in the root of our app.
touch .travis.yml
Let's add the following content to set the language to node with the node version of 10.15.0:
language: node_js
node_js:
- "10.15.0"
Now all we need to do is add this file .travis.yml
to git and push the repo changes to github.
git add .travis.yml
git commit -am "Added travis-ci configuration file"
git push github master
That's it. Now travis will execute our tests based on the default script of npm test
.
Now, we'll want travis to actually deploy our app for us. Since we already have a scripts/deploy.sh
script that will deploy our app, we can use this to deploy from travis.
To tell travis to run our deploy.sh
script after we deploy, we will need to add the deploy
key to our .travis.yml
file. We also need to build our app before deploy, hence the before_deploy
. Let's update the yml config to tell it to run our deploy script:
language: node_js
node_js:
- "10.15.0"
before_deploy:
- npm run build
deploy:
provider: script
skip_cleanup: true
script: sh scripts/deploy.sh
on:
branch: master
The next time we push, travis will take over and push up to surge (or wherever the scripts/deploy.sh
scripts will tell it to deploy).
Particulars for authentication. To deploy to github pages, we'll need to add a token to the script. The gist at https://gist.github.com/domenic/ec8b0fc8ab45f39403dd is a great resource to follow for deploying to github pages.
There are a lot of other options we have to run our tests before we deploy. This is just a getting started guide to get our application up.
The Travis CI service is fantastic for open-source projects, however to use it in a private project, we'll need to create a billable account.
An open-source CI service called Jenkins which can take a bit of work to setup (although it's getting a lot easier).
Congrats! We have our application up and running, complete with testing and all.
See you tomorrow for our last day!