GitHub Actions: Testing, Building and Notifying

GitHub Actions: Testing, Building and Notifying

In a previous article we discussed How to improve your CI/CD workflow using GitHub Actions. Today we are going a step further. We will work with some additional actions to test our application, before submit the Docker image, and sending a notification to a Slack channel.

First at all, we need to create our GitHub action using the path <our_repository_name>/.github/workflows/ and create our workflow file. For this tutorial we are going to deploy a Node.js app, so we will start by selecting the Node.js template to work as shown below.

After clicking on Set up this workflow a new file nodejs.yml is created in our repository with the following content:

name: Node.js CI

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [8.x, 10.x, 12.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm install
    - run: npm run build --if-present
    - run: npm test
      env:
        CI: true

By default it is going to build three different versions using 8.x, 10.x and 12.x running on Ubuntu, and looking for our package.json on the root directory.  

We are going to keep the default path and will build the application using version 13.x along with installing Jest for testing purposes. Other lines will remain unchanged except for the name of the action Node.js Test, Build and Notify:

name: Node.js Test, Build and Notify

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [13.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm install jest --save-dev
    - run: npm run build --if-present
    - run: npm test
      env:
        CI: true

If you have your own Node.js app code, you will have to change the test line into scripts section for "test": "jest" on your package.json

In our package.json example, this is the final code:

{
  "name": "docker-githubactions",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "Pablo Ponce",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1"
  }
}

Next, we will have to create our test files. As per Jest requirements, we are going to create the test files using filename.spec.js and store them in src/__test__/

We are going to set a silly check for demo purposes evaluating if two numbers are equals. We named the file test.spec.js but the relevant point is the path (all tests files should be included on src/__test__/)

describe("Testing our nodeJS app", () => {
  it("Testing using Github Actions", () => {
    expect(2).toBe(2);
  });
});

If the test is successful, we will add a new step into our action to build our own Docker image. We will need a Dockerfile defined in our root directory to build our application using Docker based on Node.js 13:

FROM node:13

EXPOSE 3000

WORKDIR /usr/src/app

COPY package.json package.json

RUN npm install

COPY --chown=node:node . .

CMD [ "node", "index.js" ]

Although our Node.js app is an index.js file, you should adapt the Dockerfile with your own requirements:

//Load express module with `require` directive
var express = require('express')
var app = express()

//Define request response in root URL (/)
app.get('/', function (req, res) {
  res.send('It works!')
})

//Launch listening server on port 3000
app.listen(3000, function () {
  console.log('Keep working!')
})

After that we can edit our nodejs.yml and add the Docker action made by elgohr. An important point is to set the conditional 'if' to guarantee the execution only if the previous step has been successful:

 - name: Publish Docker
      if: success()
      uses: elgohr/Publish-Docker-Github-Action@2.11
      with:
        # The name of the image you would like to push
        name: cloudblog/nodejs_githubactions
        # The login username for the registry
        username: ${{ secrets.DOCKERHUB_USER }}
        # The login password for the registry
        password: ${{ secrets.DOCKERHUB_PASS }}
Note: You need to rename the image using the format yourdockeruser/image_name_of_your_app

If everything is working fine we should have a new repository on our Docker Hub with our container image:

Additionally we would need to send a notification to a Slack channel when the build is complete and has finished correctly. Same procedure as before, we need to include a new step to be executed only if the previous step has been completed without errors.

For the Slack notification, we need to use incoming webhooks for the Slack app on our workspace. We also can override the default configuration from the webhook like the associated channel.

Here we are using the default settings when we created the webhook but writing a specific message to be sent to our #general channel with the text "A new version of your Node.js app has been built"

- name: GitHub Action for Slack
      if: success()
      env:
       SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
      uses: Ilshidur/action-slack@1.6.2
      with:
       args: 'A new version of your Node.js app has been built'

After an execution this output is sent to our Slack channel #general:

This message will  be sent only when all previous steps are completed without errors. When the tests in our code are successfully passed we get something like:

Including all configured tests passed as expected:

However please note that even if one test fails, the workflow stops and further actions will not been executed. If we modify our sample test to evaluate 3 vs 2, the test is going to fail and hence the following steps - Docker Build, Upload to Docker Hub and Notifying Slack channel - will not be executed.

Here we can see how further steps are not executed after failing the test:

The reason of the failed test is clear, we expected '2' but we are receiving '3' from our function:

Our final nodejs.yml file will look something similar like this:

name: Node.js Test, Build and Notify

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [13.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm install jest --save-dev
    - run: npm run build --if-present
    - run: npm test
      env:
        CI: true

    - name: Publish Docker
      if: success()
      uses: elgohr/Publish-Docker-Github-Action@2.11
      with:
        # The name of the image you would like to push
        name: cloudblog/nodejs_githubactions
        # The login username for the registry
        username: ${{ secrets.DOCKERHUB_USER }}
        # The login password for the registry
        password: ${{ secrets.DOCKERHUB_PASS }}


    - name: GitHub Action for Slack
      if: success()
      env:
       SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
      uses: Ilshidur/action-slack@1.6.2
      with:
       args: 'A new version of your Node.js app has been built'

Similar actions can be done using other languages, and is hence not only related to Node.js. Testing your code (syntax or real output) prior to proceed with additional actions is a really good practice to maintain health of your CI/CD pipeline. Keep digging on the GitHub Marketplace to discover more steps to be included in your workflow.