{"id":28803295,"url":"https://github.com/mscbuild/testing_node-js","last_synced_at":"2026-04-13T16:34:38.342Z","repository":{"id":298857630,"uuid":"1001333449","full_name":"mscbuild/testing_node-js","owner":"mscbuild","description":"🕵🏻‍♀️ Testing Node.js with Mocha, Chai and Sinon","archived":false,"fork":false,"pushed_at":"2025-06-13T08:42:38.000Z","size":53,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-13T09:33:35.398Z","etag":null,"topics":["chai","developer-tools","mocha","node-js","sinon","testing","testing-library","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mscbuild.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-13T07:48:28.000Z","updated_at":"2025-06-13T08:45:36.000Z","dependencies_parsed_at":"2025-06-13T09:46:31.921Z","dependency_job_id":null,"html_url":"https://github.com/mscbuild/testing_node-js","commit_stats":null,"previous_names":["mscbuild/testing_node-js"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mscbuild/testing_node-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscbuild%2Ftesting_node-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscbuild%2Ftesting_node-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscbuild%2Ftesting_node-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscbuild%2Ftesting_node-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mscbuild","download_url":"https://codeload.github.com/mscbuild/testing_node-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscbuild%2Ftesting_node-js/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260523576,"owners_count":23021966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chai","developer-tools","mocha","node-js","sinon","testing","testing-library","testing-tools"],"created_at":"2025-06-18T09:00:57.954Z","updated_at":"2026-04-13T16:34:38.336Z","avatar_url":"https://github.com/mscbuild.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Testing Node.js with Mocha, Chai and Sinon\n ![](https://komarev.com/ghpvc/?username=mscbuild) \n ![](https://img.shields.io/github/license/mscbuild/e-learning) \n![]( https://img.shields.io/github/languages/code-size/mscbuild/testing_node-js)\n![](https://img.shields.io/badge/PRs-Welcome-green)\n![](https://img.shields.io/badge/code%20style-json-green)\n![](https://img.shields.io/github/stars/mscbuild)\n![](https://img.shields.io/badge/Topic-Github-lighred)\n![](https://img.shields.io/website?url=https%3A%2F%2Fgithub.com%2Fmscbuild)\n\nTests help document the core functionality of an application. Well-written tests ensure that new features do not introduce changes that could break the application.\n\nThe engineer maintaining the codebase does not necessarily have to have written the source code. If the code is well tested, another engineer can confidently add new code or modify existing code and expect that these changes will not break other features or at least not cause unwanted side effects.\n\nJavaScript and Node.js have many testing and assertion libraries, such as Jest, Jasmine, Qunit, and Mocha. In this article, we will look at how to use Mocha for testing, Chai for assertions, and Sinon for mocks, spies, and stubs.\n\n# What is Unit Testing?\n\nUnit tests verify that functions work as expected while being isolated from other components of the application. Unit tests allow you to test different functions in the application. There are several reasons to write them:\n\n- Unit tests ensure that the code works as expected under different conditions.\n\n- Unit tests help find errors in the code early in the development process.\n\n- Since any tests that fail reveal defective code, writing unit tests helps build trust. You can be confident that your code is functional if all tests pass.\n\n # Project Setup\n\nLet's create a new directory for our user application project:\n\n~~~ruby\nmkdir mocha-unit-test \u0026\u0026 cd mocha-unit-test\nmkdir src\n~~~\nCreate a package.json file in the src folder and add the following code to it:\n\n~~~ruby\n// src/package.json\n{\n  \"name\": \"mocha-unit-test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"app.js\",\n  \"scripts\": {\n    \"test\": \"mocha './src/**/*.test.js'\",\n    \"start\": \"node src/app.js\"\n  },\n  \"keywords\": [\n    \"mocha\",\n    \"chai\"\n  ],\n  \"author\": \"Godwin Ekuma\",\n  \"license\": \"ISC\",\n   \"dependencies\": {\n    \"dotenv\": \"^6.2.0\",\n    \"express\": \"^4.18.2\",\n    \"jsonwebtoken\": \"^8.5.1\",\n    \"morgan\": \"^1.10.0\",\n    \"mysql2\": \"^2.3.3\",\n    \"pg\": \"^7.18.2\",\n    \"pg-hstore\": \"^2.3.4\",\n    \"sequelize\": \"^5.22.5\"\n  },\n  \"devDependencies\": {\n    \"chai\": \"^4.3.7\",\n    \"faker\": \"^4.1.0\",\n    \"mocha\": \"^10.2.0\",\n    \"sinon\": \"^15.0.1\"\n  }\n}\n~~~\nRun `npm install to install the project dependencies.\n\nNote that testing packages such as `mocha`, `chai, `sinon`, and `faker` are stored under dev-dependencies.\n\nThe `test` script uses a custom search pattern (glob) `./src/**/*.test.js` to set the path to test files. Mocha will look for test files (files ending in `.test.js`) in directories and subdirectories of the `src` folder.\n\n# Repositories, services and controllers\n\n- We will structure our application using the controller-service-repository pattern so that our application is divided into repositories, services, and controllers. This pattern separates the business layer of the application into three separate layers:\n\n- The repository class is responsible for retrieving and writing data from the repository. The repository is used between the service layer and the model layer. For example, in UserRepository, you will create methods to write and read user data to and from the database.\n\n- The service class calls the repository class and can combine their data to create new, more complex business objects. It is an abstraction between the controller and the repository. For example, UserService will be responsible for performing the logic needed to create a new user.\n\n- The controller contains a minimal amount of logic and is used to call services. The controller rarely calls repositories directly unless there is a good reason to do so. The controller will perform basic validations on the data received from the services in order to send a response back to the client.\n\nSplitting the application in this way makes testing easier.\n\n# UserRepository class\n\nLet's start by creating a repository class:\n~~~ruby\n// src/user/user.repository.js\nconst { UserModel } = require(\"../database\");\nclass UserRepository {\n  constructor() {\n    this.user = UserModel;\n    this.user.sync({ force: true });\n  }\n  async create(name, email) {\n    return this.user.create({\n      name,\n      email\n    });\n  }\n  async getUser(id) {\n    return this.user.findOne({ id });\n  }\n}\nmodule.exports = UserRepository;\n~~~\nThe `UserRepository` class has two methods: `create` and `getUser`. The `create` method adds a new user to the database, and the `getUser` method looks up a user in the database.\n\nLet's test the `userRepository` methods below:\n~~~ruby\n// src/user/user.repository.test.js\nconst chai = require(\"chai\");\nconst sinon = require(\"sinon\");\nconst expect = chai.expect;\nconst faker = require(\"faker\");\nconst { UserModel } = require(\"../database\");\nconst UserRepository = require(\"./user.repository\");\ndescribe(\"UserRepository\", function() {\n  const stubValue = {\n    id: faker.random.uuid(),\n    name: faker.name.findName(),\n    email: faker.internet.email(),\n    createdAt: faker.date.past(),\n    updatedAt: faker.date.past()\n  };\n  describe(\"create\", function() {\n    it(\"should add a new user to the db\", async function() {\n      const stub = sinon.stub(UserModel, \"create\").returns(stubValue);\n      const userRepository = new UserRepository();\n      const user = await userRepository.create(stubValue.name, stubValue.email);\n      expect(stub.calledOnce).to.be.true;\n      expect(user.id).to.equal(stubValue.id);\n      expect(user.name).to.equal(stubValue.name);\n      expect(user.email).to.equal(stubValue.email);\n      expect(user.createdAt).to.equal(stubValue.createdAt);\n      expect(user.updatedAt).to.equal(stubValue.updatedAt);\n    });\n  });\n});\n~~~\nIn the code above, we test the create method of the `UserRepository` class. Note that we use a stub for the `UserModel`.create method. The stub is necessary because our goal is to test the repository, not the model. The faker library is used for test data.\n~~~ruby\n// src/user/user.repository.test.js\n\nconst chai = require(\"chai\");\nconst sinon = require(\"sinon\");\nconst expect = chai.expect;\nconst faker = require(\"faker\");\nconst { UserModel } = require(\"../database\");\nconst UserRepository = require(\"./user.repository\");\n\ndescribe(\"UserRepository\", function() {\n  const stubValue = {\n    id: faker.random.uuid(),\n    name: faker.name.findName(),\n    email: faker.internet.email(),\n    createdAt: faker.date.past(),\n    updatedAt: faker.date.past()\n  };\n   describe(\"getUser\", function() {\n    it(\"should retrieve a user with specific id\", async function() {\n      const stub = sinon.stub(UserModel, \"findOne\").returns(stubValue);\n      const userRepository = new UserRepository();\n      const user = await userRepository.getUser(stubValue.id);\n      expect(stub.calledOnce).to.be.true;\n      expect(user.id).to.equal(stubValue.id);\n      expect(user.name).to.equal(stubValue.name);\n      expect(user.email).to.equal(stubValue.email);\n      expect(user.createdAt).to.equal(stubValue.createdAt);\n      expect(user.updatedAt).to.equal(stubValue.updatedAt);\n    });\n  });\n});\n~~~\nTo test the `getUser` method, we also need to stub the `UserModel.findOne` method. We use `expect(stub.calledOnce).to.be.true` to assert that the stub was called at least once. The remaining assertions check that the value returned by the `getUser` method is correct.\n\n***UserService class***\n~~~ruby\n// src/user/user.service.js\n\nconst UserRepository = require(\"./user.repository\");\nclass UserService {\n  constructor(userRepository) {\n    this.userRepository = userRepository;\n  }\n  async create(name, email) {\n    return this.userRepository.create(name, email);\n  }\n  getUser(id) {\n    return this.userRepository.getUser(id);\n  }\n}\nmodule.exports = UserService;\n~~~\nThe `UserService` class also has two methods: `create` and `getUser`. The `create` method calls the repository's `create` method, passing the name and email of the new user as arguments. The getUser method calls the repository's `getUser` method.\n\nLet's test the `userService` methods below:\n~~~ruby\n// src/user/user.service.test.js\n\nconst chai = require(\"chai\");\nconst sinon = require(\"sinon\");\nconst UserRepository = require(\"./user.repository\");\nconst expect = chai.expect;\nconst faker = require(\"faker\");\nconst UserService = require(\"./user.service\");\ndescribe(\"UserService\", function() {\n  describe(\"create\", function() {\n    it(\"should create a new user\", async function() {\n      const stubValue = {\n        id: faker.random.uuid(),\n        name: faker.name.findName(),\n        email: faker.internet.email(),\n        createdAt: faker.date.past(),\n        updatedAt: faker.date.past()\n      };\n      const userRepo = new UserRepository();\n      const stub = sinon.stub(userRepo, \"create\").returns(stubValue);\n      const userService = new UserService(userRepo);\n      const user = await userService.create(stubValue.name, stubValue.email);\n      expect(stub.calledOnce).to.be.true;\n      expect(user.id).to.equal(stubValue.id);\n      expect(user.name).to.equal(stubValue.name);\n      expect(user.email).to.equal(stubValue.email);\n      expect(user.createdAt).to.equal(stubValue.createdAt);\n      expect(user.updatedAt).to.equal(stubValue.updatedAt);\n    });\n\n//Тестирование случая, когда пользователь отсутствует.\n    it(\"should return an empty object if no user matches the provided id\", async function() {\n      const stubValue = {};\n      const userRepo = new UserRepository();\n      const stub = sinon.stub(userRepo, \"getUser\").returns(stubValue);\n      const userService = new UserService(userRepo);\n      const user = await userService.getUser(1);\n      expect(stub.calledOnce).to.be.true;\n      expect(user).to.deep.equal({})\n    });\n  });\n});\n~~~\nIn the code above, we test the `create` method of the `UserService` service. We `create` a stub for the create method of the repository. The code below tests the `getUser` method of the service:\n~~~ruby\nconst chai = require(\"chai\");\nconst sinon = require(\"sinon\");\nconst UserRepository = require(\"./user.repository\");\nconst expect = chai.expect;\nconst faker = require(\"faker\");\nconst UserService = require(\"./user.service\");\ndescribe(\"UserService\", function() {\n  describe(\"getUser\", function() {\n    it(\"should return a user that matches the provided id\", async function() {\n      const stubValue = {\n        id: faker.random.uuid(),\n        name: faker.name.findName(),\n        email: faker.internet.email(),\n        createdAt: faker.date.past(),\n        updatedAt: faker.date.past()\n      };\n      const userRepo = new UserRepository();\n      const stub = sinon.stub(userRepo, \"getUser\").returns(stubValue);\n      const userService = new UserService(userRepo);\n      const user = await userService.getUser(stubValue.id);\n      expect(stub.calledOnce).to.be.true;\n      expect(user.id).to.equal(stubValue.id);\n      expect(user.name).to.equal(stubValue.name);\n      expect(user.email).to.equal(stubValue.email);\n      expect(user.createdAt).to.equal(stubValue.createdAt);\n      expect(user.updatedAt).to.equal(stubValue.updatedAt);\n    });\n  });\n});\n~~~\nHere we again use the stub for the `getUser` method of the `UserRepository`. We also check that the stub was called at least once and that the returned value is correct.\n\n***UserController class***\n~~~ruby\n/ src/user/user.controller.js\n\nclass UserController {\n  constructor(userService) {\n    this.userService = userService;\n  }\n  async register(req, res, next) {\n    const { name, email } = req.body;\n    if (\n      !name ||\n      typeof name !== \"string\" ||\n      (!email || typeof email !== \"string\")\n    ) {\n      return res.status(400).json({\n        message: \"Invalid Params\"\n      });\n    }\n    const user = await this.userService.create(name, email);\n    return res.status(201).json({\n      data: user\n    });\n  }\n  async getUser(req, res) {\n    const { id } = req.params;\n    const user = await this.userService.getUser(id);\n    return res.json({\n      data: user\n    });\n  }\n}\nmodule.exports = UserController;\n~~~\nThe `UserController` class has two methods: register and `getUser`. Each of these methods takes two parameters: the `req` and `res` objects.\n~~~ruby\n// src/user/user.controller.test.js\n\ndescribe(\"UserController\", function() {\n  describe(\"register\", function() {\n    let status json, res, userController, userService;\n    beforeEach(() =\u003e {\n      status = sinon.stub();\n      json = sinon.spy();\n      res = { json, status };\n      status.returns(res);\n      const userRepo = sinon.spy();\n      userService = new UserService(userRepo);\n    });\n    it(\"should not register a user when name param is not provided\", async function() {\n      const req = { body: { email: faker.internet.email() } };\n      await new UserController().register(req, res);\n      expect(status.calledOnce).to.be.true;\n      expect(status.args\\[0\\][0]).to.equal(400);\n      expect(json.calledOnce).to.be.true;\n      expect(json.args\\[0\\][0].message).to.equal(\"Invalid Params\");\n    });\n    it(\"should not register a user when name and email params are not provided\", async function() {\n      const req = { body: {} };\n      await new UserController().register(req, res);\n      expect(status.calledOnce).to.be.true;\n      expect(status.args\\[0\\][0]).to.equal(400);\n      expect(json.calledOnce).to.be.true;\n      expect(json.args\\[0\\][0].message).to.equal(\"Invalid Params\");\n    });\n    it(\"should not register a user when email param is not provided\", async function() {\n      const req = { body: { name: faker.name.findName() } };\n      await new UserController().register(req, res);\n      expect(status.calledOnce).to.be.true;\n      expect(status.args\\[0\\][0]).to.equal(400);\n      expect(json.calledOnce).to.be.true;\n      expect(json.args\\[0\\][0].message).to.equal(\"Invalid Params\");\n    });\n    it(\"should register a user when email and name params are provided\", async function() {\n      const req = {\n        body: { name: faker.name.findName(), email: faker.internet.email() }\n      };\n      const stubValue = {\n        id: faker.random.uuid(),\n        name: faker.name.findName(),\n        email: faker.internet.email(),\n        createdAt: faker.date.past(),\n        updatedAt: faker.date.past()\n      };\n      const stub = sinon.stub(userService, \"create\").returns(stubValue);\n      userController = new UserController(userService);\n      await userController.register(req, res);\n      expect(stub.calledOnce).to.be.true;\n      expect(status.calledOnce).to.be.true;\n      expect(status.args\\[0\\][0]).to.equal(201);\n      expect(json.calledOnce).to.be.true;\n      expect(json.args\\[0\\][0].data).to.equal(stubValue);\n    });\n  });\n});\n~~~\nIn the first three `it` blocks, we test that the user will not be created if one or both of the required parameters (email and name) are not passed. Note that we use a stub for `res.status` and set a spy on `res.json`:\n~~~ruby\ndescribe(\"UserController\", function() {\n  describe(\"getUser\", function() {\n    let req;\n    let res;\n    let userService;\n    beforeEach(() =\u003e {\n      req = { params: { id: faker.random.uuid() } };\n      res = { json: function() {} };\n      const userRepo = sinon.spy();\n      userService = new UserService(userRepo);\n    });\n    it(\"should return a user that matches the id param\", async function() {\n      const stubValue = {\n        id: req.params.id,\n        name: faker.name.findName(),\n        email: faker.internet.email(),\n        createdAt: faker.date.past(),\n        updatedAt: faker.date.past()\n      };\n      const mock = sinon.mock(res);\n      mock\n        .expects(\"json\")\n        .once()\n        .withExactArgs({ data: stubValue });\n      const stub = sinon.stub(userService, \"getUser\").returns(stubValue);\n      userController = new UserController(userService);\n      const user = await userController.getUser(req, res);\n      expect(stub.calledOnce).to.be.true;\n      mock.verify();\n    });\n  });\n});\n~~~\nTo test the `getUser` method, we mocked the `json` method. Note that we also had to spy on the `UserRepository` by creating a new instance of `UserService`.\n\n# Conclusion\n\nRun the tests using the command below:\n~~~ruby\nnpm test\n~~~\nYou should see that the tests have passed successfully.\n\n\u003e [!NOTE]\n\u003e So, we've looked at how you can use a combination of Mocha, Chai, and Sinon to create a robust test for your Node application.\n\n\u003e [!IMPORTANT]\n\u003e Be sure to check out their documentation to expand your knowledge of these tools\n\n\u003e [!TIP]\n\u003e The full code for this article can be found on [CodeSandbox](https://codesandbox.io/s/github/GodwinEkuma/mocha-chai-unit-test).\n\n### 📝 License\n\nThis project is licensed under the `MIT License` - see the LICENSE file for details.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmscbuild%2Ftesting_node-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmscbuild%2Ftesting_node-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmscbuild%2Ftesting_node-js/lists"}