Skip to content

Instantly share code, notes, and snippets.

@ivankisyov
Last active December 15, 2021 21:45
Show Gist options
  • Save ivankisyov/9890f295bd3ef1636f77505950de38d8 to your computer and use it in GitHub Desktop.
Save ivankisyov/9890f295bd3ef1636f77505950de38d8 to your computer and use it in GitHub Desktop.
[Node] Testing

Testing with Jest

npm i --save-dev jest

package.json

{
  ...
    "scripts": {
    "test": "jest --watchAll --verbose --coverage"
  }
  ...
}

Make it faster

"scripts": {
    "test": "jest --watchAll"
  },
  "jest": {
    "testEnvironment": "node"
  }
"scripts": {
  "test": "jest --env node"
}

Jest methods

How to use toThrow()

function isInteger(num) {
  if (!Number.isInteger(Number(num))) {
    throw new Error(`${num} is not an integer`);
  }
  return true;
}
it("should throw an error if the argument is not an integer", () => {
    expect(() => {
      isInteger(13.5);
    }).toThrow();
  });

How to compare objects

it("Should remove object's properties which have a value of null or undefined", () => {
    const testObj = {
      name: "smith",
      age: null,
      member: undefined
    };
    expect(removeEmpty(testObj)).toEqual({ name: "smith" });
  });

Asynchronous testing

describe("Async tests", () => {
  describe("findUserById fn", () => {
    it("should return an user based on the passed id", async () => {
      const result = await findUserById(1);
      console.log(`@@ ===>`, result);
      expect(result.user.name).toBe("smith");
    });
  });
});
Asynchronous testing: Testing error case
const findUserById = id => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const user = users.find(user => user.id == id);

      if (!user) {
        return reject(new Error(`User with id: ${id} was not found.`));
      }

      return resolve({
        message: "User found successfully.",
        user
      });
    }, 500);
  });
};
it("should throw an error if id is not found", () => {
  return findUserById(11).catch(err => {
    expect(err.message).toBe(`User with id: 11 was not found.`);
  });
});
// alternative to the one above
it("should throw an error if id is not found [ASYNC/AWAIT]", async () => {
  try {
    await findUserById(11);
  } catch (error) {
    expect(error.message).toBe(`User with id: 11 was not found.`);
  }
});

// just for reference
// an interesting case in which the user actually exists! 
it("should throw an error if id is not found [ASYNC/AWAIT]", async () => {
  try {
    await findUserById(1); // there is an user with id of 1, so no error will be thrown here
    throw new Error("Expected Error"); // let me throw that error for you :P
  } catch (error) {
    if (error === "Expected Error") {
      throw error;
    }
    expect(error.message).toBe(`User with id: 11 was not found.`);
  }
});

Spies

Test if object's method/s have been called

// validator === object
// validateName === object's method
jest.spyOn(validator, 'validateName')
jest.spyOn(validator, 'validateEmail')
jest.spyOn(validator, 'validatePassword')      

await validator.isValid()

expect(validator.validateName).toHaveBeenCalled()
expect(validator.validateEmail).toHaveBeenCalled()
expect(validator.validatePassword).toHaveBeenCalled()

Creating a mock function

const next = jest.fn();
...
expect(next).toHaveBeenCalled()

Mock function: test how many times that fn has been called

const next = jest.fn();
expect(next).toHaveBeenCalledTimes(0)

Mock function: test the arguments that were passed to it

const sendFailure = jest.fn();
expect(sendFailure).toHaveBeenCalledWith({error: [....]}, 400)

Check for existing object's properties

expect(req.auth).toBeDefined();

Testing using mocha and nyc

package.json

{
  ...
  "scripts": {
    "test": "nyc mocha **/*.test.js",
    "test-watch": "nodemon --exec \"npm test\""
  }
  ...
}


Testing Express endpoints using Mocha, Supertest and Expect

package.json

{
  "name": "testing-in-node",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "mocha **/*.test.js",
    "test-watch": "nodemon --exec \"npm test\""
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "expect": "^1.20.2",
    "mocha": "^5.2.0",
    "supertest": "^3.3.0"
  },
  "dependencies": {
    "express": "^4.16.4"
  }
}

server/app.js

const express = require("express");
const app = express();
const port = 5000;

app.get("/", (req, res) => {
  res.send("Index Page");
});

app.get("/user", (req, res) => {
  res.send({ name: "Smith" });
});

// why:
// http://www.marcusoft.net/2015/10/eaddrinuse-when-watching-tests-with-mocha-and-supertest.html
if (!module.parent) {
  app.listen(port);
}

module.exports.app = app;

tests/app.test.js

const request = require("supertest");
const { app } = require("./../server/app");

// use for POST endpoint test
beforeEach(done => {
  // pseudo code used
  dbAPI.deleteAllUsers().then( () => done() );
})

describe("Server", () => {

  describe("GET /", () => {
    it(`should return "Index Page"`, done => {
      request(app)
        .get("/")
        .expect(200)
        .expect("Index Page")
        .end(done);
    });
  });
  
  describe("POST /user", () => {
    it(`should create new user`, done => {
      request(app)
        .post("/users")
        .send({name: "John"})
        .expect(201)
        .expect(res => {
          expect(res.body.name).toBe("John");
        })
        .end((err, res) => {
            if(err) {
                return done(err)
            }

            // check if user is saved in db
            // pseudo code used
            dbAPI.findUsers().then(users => {
                // use beforeEach()
                expect(users.length).toBe(1);
                expect(users[0].name).toBe("John");
                done();
            }).catch(err => done(err))
        });
    });
  });
  
  describe("GET /user", () => {
    it(`should return the user object`, done => {
      request(app)
        .get("/user/sjdkf-32983j-sdjhf")
        .expect(200)
        .expect(res => {
          expect(res.body).toBeA("object");
        })
        .end(done);
    });
  });
});

Tesing async functions

// simple async fn
module.exports.addNumsAsync = (a, b, cb) => {
  setTimeout(() => {
    cb(a + b);
  }, 1000);
};

// its test
const expect = require("expect");
const { addNumsAsync } = require("./../{path to the async fn}");

describe("Async methods", () => {
  it("should add two numbers async", done => {
    addNumsAsync(5, 4, sum => {
      expect(sum).toBe(9);
      done();
    });
  });
});


Additional info

Solving EADDRINUSE issue v2

/**
 * Alternative to:
 * if (!module.parent) {
 *  app.listen(port);
 * }
 */

let server;

describe("Server", () => {
    beforeEach( () => {
        server = require("./../../app");
    } )

    afterEach( () => {
        server.close();
    } )
    
    it("should...", () => {
        ...
    });
});

Run a single test

// refer to jest section to check the contents of the test command
npm test -- -t '<title of the <describe || test || it> function of your asserion library>'

Helper packages

Faker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment