Create a Heartbeat

Amy Marco
codeburst
Published in
9 min readJul 26, 2017

--

with Cypress, Buildkite, and Healthz

Why health checks?

Every website or web application aorta have a health check. Sure, you could manually pull up your website in a browser every so often, watch it load, and yell across the office, “Yep, it’s there. Still.”

But what happens when you go take a nap and the site goes down? I bet you’re not loading your website then.

The concept of automated health checks that signal when something is wrong is not new. Humans have better things to do than sit around and click a button every hour.

In fact, there are lots of tooling options to make it happen. The following post describes how to quickly set up a new or small project with the monitoring you need to have confidence in the reliability of your application. This solution can be implemented without a significant investment in time, education, or budget.

Decisions about tooling and how to test were made with the assumptions that:

  • Your system has no pre-existing system monitoring (such as munin, cacti, nagios, server spec, etc). Many of the tests we will write here would be well suited for a tool like that if it already exists in your toolbox.
  • You have no need for a larger monitoring system. If you are working on a smaller project, with a simple infrastructure, then it is easier to get going with some integration tests that will give you a read on your system health without introducing lots of requirements, languages, or complexity.
  • Your applications are written in Javascript, and you know Javascript.

For my implementation, I want to:

  • Ensure that both a simple Wordpress site and a web application (in my case, an API) and are up and running.
  • Expand my health checks into a more robust smoke test.
  • Write custom system tests; maybe I want to add a test that checks to ensure that my database and messaging queues are connected.
  • Require no human intervention.
  • Be notified if something goes wrong

Based on these requirements, the components to build with I have chosen are:

Instructions

based on OS X 10.11.6

Set up the testing repository

We’re going to create a new git repository to be responsible for running tests.

I have several other repositories that contain their own applications, and I want a centralized source from which to test all of them.

On the command line, run:

mkdir ~/heart; cd ~/heart; git init

Cyprus: Test the web

Cypress is a relatively new javascript based testing framework. It has been operating under a private Beta for a number of years and has recently been released in its open source version. I won’t try to convince you why you should use it, as I’m experimenting myself, but it suits my needs for this project. The flexibility to write a variety of testing types and its web-centric philosophy are promising for my desire to expand to smoke testing. It is also written in Javascript, which makes it ideal for many modern web applications.

Install Cypress

Requirements: npm must be installed

Install the CLI tools and desktop application

The steps for installing Cypress and getting it set up are in the docs. You’ll need to do that before we do anything fun.

The short version:

npm install -g cypress-cli #installs the CLI tools

cypress install #installs the desktop app

cypress open #opens the desktop app

From the Desktop application:

  • Enter your Github credentials.
  • Add a new project.

Set up Cypress with your testing repository

  • Click on the project ‘heart’.
    Cypress will set up your tests for you in a new folder called ‘cypress’.

You can remove the example tests that it adds for you later, but they’re helpful to look at in order to see what kinds of things you can test with Cypress.

  • Click ‘run all tests’.

…and watch all of your example tests run. The application launches a browser, shows what’s going on, and highlights the test that it is running at the same time. My heart is melting. At any point, you can click the square in the menu bar to pause the test and look more closely at what’s happened.

Configuration

In your favorite editor, open:

~/heart/cypress.json

The default contents of this file should be an empty JSON object.

Add the following:

{
“baseUrl”: “http://<YOUR_SITE>”
}

Replace <YOUR_SITE> with the url of the site you want to test. If you have a server running locally, use localhost:<PORT>

Write a test

Finally, we’re ready to write some code! I was about to have a heart attack.

For the first test, we’ll do a simple uptime check of a site.

Create a new file:

~/heart/cypress/integration/my_site_uptime.js

Inside the file, write the following:

describe(‘My site’, function() {
describe(‘homepage’, function() {
it(‘loads successfully’, function() {
cy.visit(‘/’)
cy.contains(‘My Homepage’)
})
})
})

This will load the page at the <BASE_URL> + '/'.

We’re done with our first test, unless you want to get fancy.

Improve your test

Look at the following addition to the test, and see what it is doing.

it(‘has a hamburger menu that opens’, function() {
cy.visit(‘/’)
cy.get(‘.hamburger’).click()
cy.get(‘.sidebar-open’).should(‘contain’, ‘some text’)
})

Assuming the site has a hamburger style menu, this test will click on the element for us. It’ll pass if the test matches some specific text inside the menu.

To customize this, replace:

  • 'hamburger' with a clickable element on the page.
  • 'sidebar-open' with an element that appears after clicking.
  • 'some text' with something that appears on the page.

Let’s do another

describe(‘another page’, function() {
it(‘loads successfully’, function() {
cy.visit(‘/page’)
cy.contains(‘I have another page’)
})
})

This test is similar to the very first one we wrote, but we’ve specified a page other than the homepage to visit.

To customize this, replace:

  • 'another page' with another page name on the site.
  • '/page' with the path to the page.
  • 'some text' with actual text.

The final test will look something like this:

//cypress/integration/my_site_spec.jsdescribe(‘My site’, function() {
describe(‘homepage’, function() {
it(‘loads successfully’, function() {
cy.visit(‘/’)
cy.contains(‘My Homepage’)
})

it(‘has a hamburger menu that opens’, function() {
cy.visit(‘/’)
cy.get(‘.class’).click()
cy.get(‘.sidebar-open’).should(‘contain’, ‘some text’)
})
})

describe(‘another page’, function() {
it(‘loads successfully’, function() {
cy.visit(‘/page’)
cy.contains(‘I have another page’)
})
})
})

When you’re happy, rerun cypress run to see the results.

A note about the “fancier” additions: This is the first smoke test that we’re creating in addition to the basic ping. My recommendation for adding functional tests is to keep it minimal. This set isn’t meant to exercise all your features, but to ensure that the most important parts of the site are working.

Ask yourself, what are the essential things that my users need to get value out of my site? Test those things only. Have a picky heart.

Healthz

Now we’re going to create a new test for an API. For simplicity’s sake, we’ll assume the API uses the same base url as the main site.

Before we get going, I’m going to talk about Healthz for a moment.

Healthz is a pattern for creating an endpoint which serves to collect system information and return a summary of that information in the response body. It is a simple, customizable way to get system health at a glance, from the application’s perspective. Be still, my heart!

Read Kelsey Hightower’s github repository for his canonical example of the endpoint.

We’re going to follow this idea when we create a test for an API application.

Assuming we already have this pattern set up, we might have an endpoint that looks like this when everything is healthy:

In your favorite editor, open a new file:

~/heart/cypress/integration/my_api_health.js

Write the following:

describe(‘Api’, function() {
describe(‘health check’, function() {
it(‘responds to health check with success’, function() {
cy.request(‘healthz’).then(function(response){
expect(response.status).to.eq(200)
})
})
})
})

When you run cypress run this time, you should see that there is a GET request made to the endpoint. Our “system testing” so far isn’t doing anything other than saying that our monitoring system, itself, is available. This isn’t particularly valuable, but it’s a start.

So, is the application up?

The next test is going to make use of the contents of the Healthz response, to make sure that the application is running. Assuming that the endpoint is functioning the way we want it to, writing these next tests are a walk in the park.

it('has a running application', function(){
cy.request('healthz').then(function(response){
expect(response.body.application).to.eq('running (production)')
})
})

Since that was so simple, lets add a few more tests.

If the API makes use of:

  • A database
  • A message queue
  • A mail server
  • The healthz endpoint returns something as a health check for each of these things (refer to example json response above to follow along with the matchers, as you read the code snippets below).

Then we can write a few tests that check system integration.

Database health:

it('has a connected database', function(){
cy.request('healthz').then(function(response){
expect(response.body.database).to.eq('connected')
})
})

Message queue health:

it('has a connected message queue', function(){
cy.request('healthz').then(function(response){
expect(response.body.queue).to.eq('connected')
})
})

Mail server health:

it('has an available mail server', function(){
cy.request('healthz').then(function(response){
expect(response.body.mail).to.eq('available')
})
})

The full test:

describe('Api', function() {
describe('health check', function() {
it('responds to health check with success', function() {
cy.request('healthz').then(function(response){
expect(response.status).to.eq(200)
})
})
it('has a running application', function(){
cy.request('healthz').then(function(response){
expect(response.body.application).to.eq('running (production)')
})
})
it('has a connected database', function(){
cy.request('healthz').then(function(response){
expect(response.body.database).to.eq('connected')
})
})
it('has a connected message queue', function(){
cy.request('healthz').then(function(response){
expect(response.body.queue).to.eq('connected')
})
})
it('has an available mail server', function(){
cy.request('healthz').then(function(response){
expect(response.body.mail).to.eq('available')
})
})
})
})

Our final test has results that will tell us if any required parts of our system are not working as expected. This test really gets to the heart of things.

Build Kite Automation

You will need an account at Build Kite for the following steps.

You will need:

  • A Github account connected to Build Kite.
  • A running Build Kite Agent, set up on a machine that is reliably connected to the internet (a Digital Ocean droplet would work well for this). If you don’t have this, click on ‘Agents’ in the top menu, and follow the Quickstart guide.

Set up a webhook in Github

In order to have tests run when a pull request is created (recommended) or there is an update to the master branch, do the following:

  • Navigate to your Github account settings page.
  • Click on ‘Webhooks’.
  • Create a new webhook and configure it.
  • You will need to get a webhook URL from your Build Kite account page, and enter that into the ‘Payload URL’ field.
  • The content type will be application/json
  • Select ‘Let me select individual events’ and check off the following: ‘Push’, ‘Deployment’, ‘Pull Request’, and ‘Active’.

Create a new Build Kite pipeline

  • Click on ‘Pipelines’ in the top menu.
  • Click on the ‘+’ located in the top right, and fill out the information in the resulting form.

You should see a button that says ‘New Build’.
Click it, and see the tests run!

Schedule the build

  • Click on Pipeline settings.
  • Select ‘Schedules’ from the left nav.
  • Click ‘New Schedule’.
  • Fill out the form. Buildkite accepts Cron Interval values in the form of @hourly, @daily, @monthly, so select an option that suits you, or use standard cron format to get more specific.
  • Save, and return to the pipeline.

Get notified

It is important that we get a notice when a build failed, otherwise we won’t wake up from our naps in time to save the day.

  • Click on ‘Pipeline Settings’.
  • Select ‘Personal Email Settings’ from the left nav.

Configure it how you like. The settings in the picture below are recommended in order to get an email whenever any build fails.

Rock and roll.

--

--