神刀安全网

Node Hero – Node.js Unit Testing Tutorial

This article is the 9th part of the tutorial series called Node Hero – in these chapters, you can learn how to get started with Node.js and deliver software products using it.

In this tutorial, you are going to learn what is unit testing in Node.js, and how to test your applications properly.

Upcoming and past chapters:

  1. Getting started with Node.js
  2. Using NPM
  3. Understanding async programming
  4. Your first Node.js HTTP server
  5. Node.js database tutorial
  6. Node.js request module tutorial
  7. Node.js project structure tutorial
  8. Node.js authentication using Passport.js
  9. Node.js unit testing tutorial [you are reading it now]
  10. Debugging Node.js
  11. Securing your application
  12. Deploying Node.js application to a PaaS
  13. Monitoring and operating Node.js applications

Testing Node.js Applications

You can think of tests as safeguards for the applications you are building. They will run not just on your local machine, but also on the CI services so that failing builds won’t get pushed to production systems.

You may ask: what should I test in my application? How many tests should I have?

The answer varies across use-cases, but as a rule of thumb, you can follow the guidelines set by the test pyramid .

Node Hero - Node.js Unit Testing Tutorial

Essentially, the test pyramid describes that you should write unit tests , integration tests and end-to-end tests as well. You should have more integration tests than end-to-end tests, and even more unit tests.

Let’s take a look at how you can add unit tests for your applications!

Please note, that we are not going to talk about integration tests and end-to-end tests here as they are far beyond the scope of this tutorial.

Unit Testing Node.js Applications

We write unit tests to see if a given module (unit) works. All the dependencies are stubbed, meaning we are providing fake dependencies for a module.

You should write the test for the exposed methods, not for the internal workings of the given module.

The Anatomy of a Unit Test

Each unit test has the following structure:

  1. Test setup
  2. Calling the tested method
  3. Asserting

Each unit test should test one concern only. (Of course this doesn’t mean that you can add one assertion only) .

Modules Used for Node.js Unit Testing

For unit testing, we are going to use the following modules:

  • test runner : mocha , alternatively tape
  • assertion library : chai , alternatively the assert module (for asserting)
  • test spies, stubs and mocks : sinon (for test setup) .

Spies, stubs and mocks – which one and when?

Before doing some hands-on unit testing, let’s take a look at what spies, stubs and mocks are!

Spies

You can use spies to get information on function calls, like how many times they were called, or what arguments were passed to them.

it('calls subscribers on publish', function () {     var callback = sinon.spy()   PubSub.subscribe('message', callback)    PubSub.publishSync('message')    assertTrue(callback.called) }) // example taken from the sinon documentation site: http://sinonjs.org/docs/ 

Stubs

Stubs are like spies, but they replace the target function. You can use stubs to control a method’s behaviour to force a code path (like throwing errors) or to prevent calls to external resources (like HTTP APIs).

it('calls all subscribers, even if there are exceptions', function (){     var message = 'an example message'   var error = 'an example error message'   var stub = sinon.stub().throws()   var spy1 = sinon.spy()   var spy2 = sinon.spy()    PubSub.subscribe(message, stub)   PubSub.subscribe(message, spy1)   PubSub.subscribe(message, spy2)    PubSub.publishSync(message, undefined)    assert(spy1.called)   assert(spy2.called)   assert(stub.calledBefore(spy1)) }) // example taken from the sinon documentation site: http://sinonjs.org/docs/ 

Mocks

A mock is a fake method with a pre-programmed behavior and expectations.

it('calls all subscribers when exceptions happen', function () {     var myAPI = {      method: function () {}    }    var spy = sinon.spy()   var mock = sinon.mock(myAPI)   mock.expects("method").once().throws()    PubSub.subscribe("message", myAPI.method)   PubSub.subscribe("message", spy)   PubSub.publishSync("message", undefined)    mock.verify()   assert(spy.calledOnce) // example taken from the sinon documentation site: http://sinonjs.org/docs/ }) 

As you can see, for mocks you have to define the expectations upfront.

Imagine, that you’d like to test the following module:

const fs = require('fs')   const request = require('request')  function saveWebpage (url, filePath) {     return getWebpage(url, filePath)     .then(writeFile) }  function getWebpage (url) {     return new Promise (function (resolve, reject) {     request.get(url, function (err, response, body) {       if (err) {         return reject(err)       }        resolve(body)     })   }) }  function writeFile (fileContent) {     let filePath = 'page'   return new Promise (function (resolve, reject) {     fs.writeFile(filePath, fileContent, function (err) {       if (err) {         return reject(err)       }        resolve(filePath)     })   }) }  module.exports = {     saveWebpage } 

This module does one thing: it saves a web page (based on the given URL) to a file on the local machine. To test this module we have to stub out both the fs module as well as the request module.

Before actually starting to write the unit tests for this module, at RisingStack, we usually add a test-setup.spec.js file to do basics test setup, like creating sinon sandboxes. This saves you from writing sinon.sandbox.create() and sinon.sandbox.restore() after each tests.

// test-setup.spec.js const sinon = require('sinon')   const chai = require('chai')  beforeEach(function () {     this.sandbox = sinon.sandbox.create() })  afterEach(function () {     this.sandbox.restore() }) 

Also, please note, that we always put test files next to the implementation, hence the .spec.js name. In our package.json you can find these lines:

{   "test-unit": "NODE_ENV=test mocha '/**/*.spec.js'", } 

Once we have these setups, it is time to write the tests itself!

const fs = require('fs')   const request = require('request')  const expect = require('chai').expect  const webpage = require('./webpage')  describe('The webpage module', function () {     it('saves the content', function * () {     const url = 'google.com'     const content = '<h1>title</h1>'     const writeFileStub = this.sandbox.stub(fs, 'writeFile', function (filePath, fileContent, cb) {       cb(null)     })      const requestStub = this.sandbox.stub(request, 'get', function (url, cb) {       cb(null, null, content)     })      const result = yield webpage.saveWebpage(url)      expect(writeFileStub).to.be.calledWith()     expect(requestStub).to.be.calledWith(url)     expect(result).to.eql('page')   }) }) 

The full codebase can be found here: https://github.com/RisingStack/nodehero-testing

Code coverage

To get a better idea of how well your codebase is covered with tests, you can generate a coverage report.

This report will include metrics on:

  • line coverage,
  • statement coverage,
  • branch coverage,
  • and function coverage.

At RisingStack, we use istanbul for code coverage. You should add the following script to your package.json to use istanbul with mocha :

istanbul cover _mocha $(find ./lib -name /"*.spec.js/" -not -path /"./node_modules/*/")   

Once you do, you will get something like this:

Node Hero - Node.js Unit Testing Tutorial

You can click around, and actually see your source code annotated – which part is tested, which part is not.

Next up

Testing can save you a lot of trouble – still, it is inevitable to also do debugging from time to time. In the next chapter of Node Hero, you are going to learn how to debug Node.js applications.

If you have any questions or recommendations for this topic, write them in the comments section.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Node Hero – Node.js Unit Testing Tutorial

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址