Automated Testing and Continuous Integration

In this lab, we were practising the use of automated testing and continuous integration.

I chose to use Jest as my testing framework because it is suitable for Node. Jest is a JavaScript testing framework that was developed by Facebook. In order to start using Jest in the project, first, you need to install it:

npm install --save-dev jest

Then add the below section to your package.json file:

{
"scripts": {
"test": "jest"
}
}

To run the tests in your project :

npm run test

Writing Unit Tests:

In order to create better unit tests, I refactored the code and placed functions into separate modules.

For my first unit test, I choose to test the printResponse function(located in result.js file)

const chalk = require(‘chalk’);//print responses colorizedmodule.exports.printResponse = function printResponse(data) {for (var item of data) {if (item.status == 200) {console.log(chalk.green.bold(`[GOOD] Status: [${item.status}] ${item.url}`));} else if (item.status == 400 || item.status == 404) {console.log(chalk.red.bold(`[BAD] Status: [${item.status}] ${item.url}`));} else {console.log(chalk.grey.bold(`[UNKNOWN] Status: [${item.status}] ${item.url}`));}}};
  • First I created the test file named result.test.js. Your test file should have the same name as the file that is being tested. After the file name, you need to add .test.js.
  • You need to include the module that has the function that needs to be tested.
  • To test this function, I had to assign the console output to the constant variable before executing the tests and reset it back to the normal after the tests are completed.
  • I created tests for each status code to check that the output gets colourized based on the status response.
  • In addition, created a test that passes an empty array to see that nothing is got printed.

In my code, I use fetch library to fetch URL responses. To mock fetch calls, I used Jest Fetch Mock that allows you to easily mock your fetch calls and return the response you need to fake the HTTP requests.

Installation:

npm install --save-dev jest-fetch-mock

If you want to use fetch mock globally(ie. to make it accessible in all tests):

  • Create a setupJest.js file to setup the mock or add this to an existing setup file:
//setupJest.js or similar filerequire('jest-fetch-mock').enableMocks()

Then add the setupFile to your jest config in package.json:

"jest": {    "automock": false,    "setupFiles": [        "./setupJest.js"
]
}

An example of using jest fetch mock :

describe(‘Urls responses tests’, () => {beforeEach(() => {fetch.resetMocks();});test(‘Url with response status 200 should be considered as GOOD’, async () => {const url = ‘https://google.ca';const statusResponseForUrl = { url: `${url}`, status: `200` };fetch.mockResponseOnce(‘{ “id”: 1 }’, { status: 200 });const data = await testUrl(url);expect(data).toEqual(statusResponseForUrl);});
});
  • Before each test — need to reset the fetch mock.
  • fetch.mockResponseOnce(‘{ “id”: 1 }’, { status: 200 }); → fetch.mockResponseOnce(bodyOrFunction, init): fetch - Mock each fetch call independently

In order to run code coverage analysis needed to add the below to the package.json:

“scripts”: {“coverage”: “jest — collectCoverage”},

To run code coverage:

npm run coverage

When I ran the coverage analysis I found that 1 line never get hit in the function that responsible to get the responses for the URL.

try {const urlTest = await fetch(url, { method: ‘head’, timeout: 1500 });statusResponseForUrl = { url: `${url}`, status: `${urlTest.status}` };} catch (err) {statusResponseForUrl = { url: `${url}`, status: ‘UNKNOWN’ };<-- This}

In order to cover this line I had to write a test that mocks reject from fetch as follow:

test(‘Url with no response should be considered as UNKNOWN’, async () => {const url = ‘https://google.ca';const statusResponseForUrl = { url: `${url}`, status: `UNKNOWN` };fetch.mockReject(new Error(‘errors’));const data = await testUrl(url);expect(data).toEqual(statusResponseForUrl);});

After writing this test, the code coverage analysis showed that all lines were covered.

To set up a GitHub Action CI Workflow I did the following steps:

  • Go to the project repo on Github
  • Click on the Action tab
  • Selected the Node.js workflow and clicked “Set up this workflow”
  • For node version selected only 10

My YAML file looks as follow:

name: Node.js CIon:push:branches: [ master ]pull_request:branches: [ master ]jobs:build:runs-on: ubuntu-lateststrategy:matrix:node-version: [10.x]steps:- uses: actions/checkout@v2- name: Use Node.js ${{ matrix.node-version }}uses: actions/setup-node@v1with:node-version: ${{ matrix.node-version }}- run: npm ci- run: npm run build — if-present- run: npm test

After setting up the CI workflow I wanted to make sure that my CI workflow is correct so I created e2e tests, pushed the changes and created a pull request against my own repo. My pull request has triggered the CI workflow and it passed the tests.

This part was a bit challenging because my partner's link checker is using different libraries and code structure than my link checker. The testing setup was similar we both used jest as the testing framework. I found difficulties writing additional tests because the function that needed additional testing was too long. In my opinion, this function could be refactored in order to allow better testing.

I have experience writing automation testing as part of my previous co-op job in the Ministry of Education, CSC Cluster. However, these tests were a bit different. I tested applications that already were deployed.

In this lab, I learned how to write automation tests that check the program before it is going “live”.

I think it SUPER important to write test cases and test your program. We all humans and make mistakes. These tools help us to catch it and to deliver a higher quality product. On the same note, when you write tests you find bugs which teaches you a lot and makes you a better developer!