DEV Community

shang8024
shang8024

Posted on

Testing React+Node JS app with Jest and Upload coverage report to CodeClimate with Github Action

It is easy to fall into a mindset when we are coding. When writing tests, we often need to consider various situations and make us re-examine our code, which is called Test-Driven-Development.
Writing tests can bring us certain benefits:

  • To verify the correctness of the code

  • To avoid errors when modifying code

  • To avoid errors when other team members modify your code

  • To facilitate automated testing and deployment

As your app grows larger, it becomes more complex, harder to maintain, and more buggy. Therefore, it's essential to write maintainable and testable code at very early start.
Coverage reports tell you how much code the test cases cover. It provides useful metrics that can help you assess the quality of your test suite. We then can use CodeClimate to analyze the quality of our code from the coverage reports.

Prerequisites

Suppose you have already created a React+Node JS app with npm create-react-app and npm init, setup express, and maybe have written some routes.
Likely, you will have a similar folder hierarchy in your monorepo as follows:

client
| public
| src
  | App.js
  | index.js
  | app.test.js
  | setupTests.js
  | reportWebVitals.js
| package.json
server
| routes
  | sample.route.js
| package.js
Enter fullscreen mode Exit fullscreen mode

Tesing your react frontend with Jest and React Testing Library

If you are using create-react-app, both Jest and React Testing Library are built-in, so we don't need to install them separately.

By default, Jest will look for files with the .test.js suffix and files with the .js suffix in __tests__ folders.

An example app.test.js for testing components can be:

import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';

it('renders welcome message', () => {
  render(<App />);
  expect(screen.getByText('Learn React')).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

Testing your node backend with Jest and supertest

To start, you will need to first install the following packages by npm i jest supertest.

  • jest: Jest is a JavaScript testing framework built on top of Jasmine. Unit testing is the main usage of it.

  • supertest: Supertest is a SuperAgent driven library for testing HTTP servers. With wich, we can test endpoints and routes.

Then in package.json, add

  "scripts": {
    ...
    "test":  "jest"
  },
Enter fullscreen mode Exit fullscreen mode

We will write a sample route first.
In server/routes/sample.test.js, suppose we have two functions for getting all users and add user.

const express = require("express")
const router = express.Router()
router.get("/getUsers",getUsers)
router.post("/createUser",createUser)

module.exports = router
Enter fullscreen mode Exit fullscreen mode

And in server/tests/sample.test.js

const request = require("supertest");
const express = require("express");
const app = express();
app.use(express.json());
app.use("/", router);
describe("GET /getUsers", () => {
  it("should return all users", async () => {
    const res = await request(app).get("/getUsers");
    expect(res.statusCode).toBe(200);
    expect(res.body.length).toBeGreaterThan(0);
  });
});
describe("POST /createUser", () => {
  it("should not create with empty name", async () => {
    const req = {name: "",password: "123"};
    const res = await request(app).post("/createUser").type('json').send(req);
    expect(res.statusCode).toBe(400);
  });
  it("should create a user ", async () => {
    const req = {name: "user",password: "123"};
    const res = await request(app).post("/createUser").type('json').send(req);
    expect(res.statusCode).toBe(200);
    expect(res.body.name).toBe("user");
  });
});
Enter fullscreen mode Exit fullscreen mode

Testing Express+Mongodb

If you are testing with a mongodb, you should always make sure your database is seperate, that the datasets for each test don't interfere with each other. I would recommand using local mongodb.

You should put your mongodb url in .env and import dotenv to get the environment variable. Install dotenv by npm i dotenv.

In server/.env

MONGODB_URI="mongodb://localhost:27017"
Enter fullscreen mode Exit fullscreen mode

You'll need to connect and disconnect the database before and after each test.
In server/tests/mongo.test.js

const mongoose = require("mongoose");
const request = require("supertest");
const app = require("../app");
require("dotenv").config("../.env");

beforeEach(async () => {
  await mongoose.connect(process.env.MONGODB_URI);
});

afterEach(async () => {
  await mongoose.connection.dropDatabase();
  await mongoose.connection.close();
});
Enter fullscreen mode Exit fullscreen mode

Generate test coverage report

In the Code Climate documentation, the supported coverage tool for JavaScript is lcov (generated by Istanbul).
In both client and server folders:

  • At root, add the nyc package with npm install --save-dev nyc

  • In package.json, add:

  "jest": {
    "coverageReporters": [
      [
        "lcov",
        {
          "projectRoot": ".."
        }
      ]
    ]
  },
  "nyc": {
    "reporter": [
      "lcov"
    ]
  }
Enter fullscreen mode Exit fullscreen mode

To make jest and nyc generate lcov reports. You can also add other reporters like text, text-summary.

  • For your react frontend, add to the package.json:
  "scripts": {
    ...
    "coverage":  "react-scripts test --coverage --watchAll=false"
  },
  ...
  "jest": {
    ...
    "transformIgnorePatterns": ["node_modules\/(?!axios)"],
    "collectCoverageFrom": [
      "src/**/*.js",
      "!**/node_modules/**",
    ]
  }
Enter fullscreen mode Exit fullscreen mode

At the root of client folder, run npm coverage to see the coverage report.

  • Similarly in the node backend, add to the scripts in package.json:
  "scripts": {
    ...
    "coverage": "jest --coverage"
  },
  ...
  "jest": {
    ...
    "transformIgnorePatterns": ["node_modules\/(?!axios)"],
    "testEnvironment": "node",
    "collectCoverageFrom": [
      "**/routes/**/*.js",
      "!**/node_modules/**",
    ]
  }
Enter fullscreen mode Exit fullscreen mode

At the root of server folder, run npm coverage to see the coverage report.

We can use the property collectCoverageFrom to specify which files need to be collected, and which files should not be collected (node_modules, config.js, datasets, and so on).

Upload coverage report to CodeClimate

Note that you need at least some test coverage to generate a report that is able to be uploaded to CodeClimate, so make sure you followed the previous parts carefully.

To start, make sure you already have a CodeClimate account.

On the dashboard, click on "Add a repository" and then "add repo" to link your repo to CodeClimate.

Go to repo setting/test coverage and find the TEST REPORTER ID

In your Github repo, click on setting/secrets/action, and add the CC_TEST_REPORTER_ID as a new secret key.

Below is an example of the github-action.yml

name: build
on: 
  workflow_dispatch:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  Coverage:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      matrix:
        node-version: [14.x]
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - name: Test Coverage Frontend
      run: |
        cd client
        npm install
        npm coverage

    - name: Test Coverage Backend
      run: |
        cd server
        cp -r .env.example .env
        npm install
        npm coverage

    - name: Test & publish code coverage
      uses: paambaati/codeclimate-action@v3.2.0
      env:
        CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}
      with:
        coverageLocations: |
          ${{github.workspace}}/client/coverage/lcov.info:lcov
          ${{github.workspace}}/server/coverage/lcov.info:lcov
Enter fullscreen mode Exit fullscreen mode

In order to upload the two coverage tests for react and node at once, we need to set the projectRoot of the lcov coverage reporter of jest to the root of our monorepo (using ".." in package.json).

Top comments (0)