Top 4 Unit Testing Frameworks for Node.js

Top 4 Unit Testing Frameworks for Node.js

In this Node.js unit testing tutorial, you'll see top 4 unit testing frameworks for Node.js. We will compare some of the most popular and widely used frameworks: Jasmine, Jest, AVA, and Mocha.

Introduction

If you are reading this article, you probably know what Node.js is. Node.js is often used in creating APIs. It has steadily grown in popularity since its initial release in 2009.

According to SimilarTech there are over 85000 unique domains using Node.js today. Stackshare lists Uber, Twitter, Netflix, Medium, Slack, Reddit, and eBay as companies using Node.js.

In this post, we will be looking at unit testing in Node.js. For those of you who don’t know, unit testing is a software testing method where individual pieces of code (usually the smallest piece of code that can be logically isolated in a system) are tested in isolation. Unit tests should be isolated such that there are no external dependencies. Let’s look at some advantages and disadvantages of unit testing.

Advantages of unit testing:

  • It makes it easier to identify bugs in code earlier. Appropriate test cases should be written for every piece of code to ensure that it meets specifications and provides the desired output. Any changes that result in failing tests will indicate that an error or bug has been introduced. Additionally, unit testing makes it easier to narrow down the offending piece of code
  • Unit tests act as self-documentation. A new team member can gain a better understanding of the codebase by going through unit tests
  • The debugging process is made a lot easier. This is because when the test fails, the focus will be on the latest changes made
  • Refactoring code is made easier since changes can be verified using tests to ensure that the unit being tested still behaves in the desired manner
  • Costs that would be incurred fixing bugs or due to system outages occasioned by bugs are reduced

Testing frameworks provide a set of reusable components or modules such as test runners and utilities for running automated tests. The testing framework is responsible for:

  1. Describing the format used to convey test expectations
  2. Creating a way of accessing the application or code to be tested
  3. Executing the tests
  4. Reporting test results

They are particularly useful when tests are an integral part of your continuous integration process. Frameworks are built for a specific type of testing; unit, integration, functional or combinations of these.

What makes a good testing framework?

Testing frameworks are a dime a dozen. In order to pick something that works for your use case, you need to evaluate each framework based on your needs and characteristics. Below are a few points that are essential for a good testing framework:

  1. Ease of setup – getting up and running with your tests should take a minimal amount of effort
  2. Well supported – there is plenty of excellent documentation and communities to get help
  3. A wide array of feature sets – the framework has things such as matchers, spies, mocking in-built
  4. Speed – for tests that are CPU bound, choosing the right framework can save you a lot of time during test runs
  5. Ease of reporting – coverage reports should be easy to generate using in-built reporting or external reporting libraries should be easy to intergrate
  6. A good testing library should be easy to integrate into your continuous integration process
Comparison of unit testing frameworks

According to the State of JS 2018 survey, the most popular JavaScript testing frameworks and libraries are:

In this post, we will compare some of the most popular and widely used frameworks; Jasmine, Jest, AVA, and Karma.

1. Jest

Jest is a JavaScript testing framework developed and maintained regularly by FACEBOOK. It rose in popularity in 2017. In 2016, only 6% of people surveyed for the State of JS had used Jest before as opposed to 25% in the 2017 survey. It is based on Jasmine but has been greatly improved since the early days.

Pros

  • Jest is well documented. The Jest documentation has a lot of detailed instructions on how to set up testing, writing different types of tests, utilizing its many features as well as great examples.
  • Easy to set up – flexible and easy configuration and less boilerplate code than other frameworks
  • Parallel test running
  • Fast – tests are parallelized by running them in their own processes to maximize performance
  • It includes features like snapshots, coverage, and test watching

Cons

  • Jest displays multiple error messages for the same error
  • It can require more dependencies during initial setup (e.g babel)

Sample Jest test:

describe("Sum numbers", () => {
  test("it should sum two numbers correctly", () => {
    const sum = 1 + 2;
    const expectedResult = 3;
    expect(sum).toEqual(expectedResult);
  })
});
2. Jasmine

Jasmine, on the other hand, has been around for a lot longer. It was developed by Pivotal Labs and released in 2010. It aims to run on any JavaScript-enabled platform and is highly flexible and compatible with a variety of other testing frameworks and libraries like Sinon and Chai. Additionally, due to its longevity, it has developed a significant community and support around it with lots of libraries, blog articles, and tutorials.

Pros

  • Simple to setup. Jasmine has a CLI tool that creates a spec folder and a JSON configuration file. With one command you are ready to start testing your code
  • It has been around for a long time and is thoroughly tested, documented
  • It is behavior-driven development focused with descriptive syntax
  • It’s supported by many CI servers with plugins available for some of those that don’t have out of the box support

Cons

  • Unfriendly error logs
  • Asynchronous testing can be quite a hustle. Testing asynchronous code often requires more code and tweaks than other testing frameworks with inbuilt promise support
  • Test files must have a specific suffix (*spec.js)
  • In the past, there have been complaints about the maintenance team not being very responsive to pull requests or issues. A look at their open pull requests shows that this is improving however
  • The assertion library is not as rich as Chai

Sample Jasmine test:

describe("Sum numbers", function() {
  it("should sum two numbers correctly", function() {
    var sum = 1 + 2;
    var expectedResult = 3;
    expect(sum).toEqual(expectedResult);
  });
});
3. AVA

Minimalism is the focus of AVA. It has a simple API while still supporting advanced features. It is quite fast and achieves this by running tests in parallel as separate Node.js processes. Unlike other testing frameworks such as Jest and Jasmine, it does not create test globals.

Pros

  • It’s simple and easy to use. To install and setup AVA, all you have to do is run npm init ava
  • Parallel test running
  • Native ES6/ES7 support
  • It has built-in support for async functions
  • If a promise is returned, you don’t need to end the test yourself. It will end when the promise resolves

Cons

  • AVA is relatively new. The community is still growing and there isn’t a lot of documentation or tutorials like other testing frameworks
  • AVA has a lot of open issues at the moment

Sample Ava test:

import test from 'ava';
test('Sum numbers', t => {
  const sum = 1 + 2;
  const expectedResult = 3;
  t.equal(sum, expectedResult);
});
4. Mocha

Mocha, like Jasmine, has been in existence for quite a while. It was initially released in November 2011. However, unlike other frameworks like Jest and Jasmine, it relies on third-party assertions, mocking, and spying tools(Spies are objects that keep track of their interaction with other objects or pieces of code. They keep a record of things such as the number of calls, arguments passed to specific functions and return values which can be used to make assertions.) e.g Sinon and Chai. It is very extensible and has a lot of plugins, extensions, and libraries designed to run on top of it.

Pros

  • Highly extensible and therefore has support for different assertion and mocking libraries
  • Easy asynchronous testing
  • Adding support for generators to test suites is relatively easy. Using the co-mocha package, all you have to do is require it in your tests and you are ready to use generators
  • Supported by some CI servers and plugins for others

Cons

  • The use of extra libraries can introduce configuration complexity and also increases maintenance work
  • No auto mocking available

Sample Mocha test:

const { expect } = require('chai');
    describe('Sum numbers', () => {
      it('should add two numbers correctly', () => {
        const sum = 1 + 2;
        const expectedResult = 3;
        expect(sum).to.equal(expectedResult);
      });
    });

The table below shows a comparison of the features across the different frameworks:

Choosing the best framework

The best framework can vary based on your needs, project size, and other factors. What works now might not work in the future. It is important to take both your current and future needs into consideration when choosing the right framework.

If you want to hit the ground running, you cannot go wrong with Jest. It is an extremely fast framework, easy to set up and has a lot of built-in features to help you with your testing.

When it comes to simplicity, AVA is your cup of tea. It is minimal, simple, but capable of handling various types of test. It is also fairly fast.

Mocha is the best choice for someone that wants flexible configurations as well as a choice of libraries to use together with it.

Conclusion

There are very many frameworks and libraries that you can use to test your Node.js projects. In this article, we have focused on four of the most popular frameworks. Remember, your choice of testing framework will depend on your needs. In some cases, some libraries will be deficient and in others, they will be overkill.

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

JavaScript Testing - Unit Tests, Integration Tests & e2e Tests

JavaScript Testing - Unit Tests, Integration Tests & e2e Tests

JavaScript testing - i.e. unit tests, integration tests and e2e (UI) tests - can be intimidating. It shouldn't be! This video guides you through all the basics (including the "Why"?) of JavaScript testing. JavaScript Testing Introduction Tutorial - Unit Tests, Integration Tests & e2e Tests. Master JavaScript testing now!

JavaScript testing - i.e. unit tests, integration tests and e2e (UI) tests - can be intimidating. It shouldn't be! This video guides you through all the basics (including the "Why"?) of JavaScript testing. Master JavaScript testing now!

Javascript Testing Selenium Automation Nightwatch js Nodejs

Javascript Testing Selenium Automation Nightwatch js Nodejs

Simpliv offers solid learning on the basics of Selenium Automation Scripts and how they help automate the browser. By the end of this highly practical course, you will be able to build a Testing Framework from scratch. All at just $9! Enroll today!

Description
We will go over of basics such as what is selenium and how it help automate the browser.

To complete Setup and Installation of Nightwatch js Testing framework and we will create Test plan and test case, we will manually test a real website and then take those test case and we will automate it.

As we build our framework we will see how page object model/pattern help us re-use our code and how scale our test cases with ease

Who is the target audience?

Manual QA, SDET,Developer, Devops and anyone Interested in Learning Automation Testing
Basic knowledge
Beginner Level understanding on Software Testing
What will you learn
Basics of Selenium Automation Scripts, by the end build a Testing Framework from scratch

JavaScript Testing using Selenium WebDriver, Mocha and NodeJS

JavaScript Testing using Selenium WebDriver, Mocha and NodeJS

In case you are looking to write a functional test in JavaScript, the following tutorial provides UI automation engineers with the perfect structural reference material for JavaScript testing with Selenium WebDriver 3, Mocha and NodeJS.

In case you are looking to write a functional test in JavaScript, the following tutorial provides UI automation engineers with the perfect structural reference material for JavaScript testing with Selenium WebDriver 3, Mocha and NodeJS.

These days, JavaScript is a ubiquitous web language which seems to overcome its ‘notorious’ past and has become a more solid platform not only for client-side, but for server domains too. Mocha.js, or simply Mocha, is a feature-rich JavaScript test framework running on Node.js, which provides the platform and the API for building standalone applications server-side using Google’s V8 JavaScript engine at its base.

*Note: to get started with this JavaScript tutorial, you’ll need to be familiar with the basics of NodeJS and the JavaScript programming language.

Tutorial Overview:

1. Mocha Test Construction
  • Introduction
  • Installation
  • Installing Chai Assertion Module
  • Test suite and Test Case Structure
  • Constructing Tests with Mocha
  • Running Mocha’s Test Suite and Test Cases
  • Managing Syncing of Async Testing Code
2. Using Javascript Selenium 3 API Integrated with MochaJS
  • Selenium Introduction
  • Selenium Installation
  • WebDriver Construction
  • Integrating MochaJS with Selenium WebDriver 3

Versions used:

  • Node version used: 6.10.1 (LTS)
  • Mocha: 2.5.3
  • WebDriverJS: 3.3.0
1. Constructing Tests with Mocha

Introduction to Mocha

As mentioned, Mocha is a JavaScript test framework that runs tests on Node. Mocha comes in the form of a Node package via npm, allowing you to use any library for assertions as a replacement to Node’s standard ‘assert’ function, such as ChaiJS.

Mocha provides an API, which specifies a way to structure the testing code into test suites and test case modules for execution, and later on to produce a test report. Mocha provides two modes for running: either by command line (CLI) or programmatically (Mocha API).

Install Mocha

If Mocha is to be used in CLI, then it should be installed globally as Node.js.

npm install -g mocha 

Install Chai Assertion Module

npm install --save chai 

The –save option is used to install the module in the project’s scope and not globally.

Test Suite and Test Case Structure

In Mocha, a test suite is defined by the ‘describe’ keyword which accepts a callback function. A test suite can contain child / inner test suites, which can contain their own child test suites, etc. A test case is denoted by the ‘it’ function, which accepts a callback function and contains the testing code.

Mocha supports test suite setup and test case setup functions. A test suite setup is denoted by before while a test case setup applies beforeEach. beforeEach is actually a common setup for every case in the suite, and will be executed before each case.

As with the setup, Mocha supports test suite and test case teardown functions. A test suite teardown is denoted by after, while a test case teardown is implemented with afterEach, functions that are executed after a test suite and after each test case respectively.

Create a file that will ‘host’ the test suite, e.g. test_suite.js, and write the following to it;

describe("Inner Suite 1", function(){

    before(function(){

        // do something before test suite execution
        // no matter if there are failed cases

    });

    after(function(){

        // do something after test suite execution is finished
        // no matter if there are failed cases

    });

    beforeEach(function(){

        // do something before test case execution
        // no matter if there are failed cases

    });

    afterEach(function(){

        // do something after test case execution is finished
        // no matter if there are failed cases

    });

    it("Test-1", function(){

        // test Code
        // assertions

    });

    it("Test-2", function(){

        // test Code
        // assertions

    });

    it("Test-3", function(){

        // test Code
        // assertions

    });

});

Running Mocha Test Suite and Test Cases

Mocha supports execution of tests in three ways: Whole Test Suite file, tests filtered by “grep” patterns and tests grep filtering looking in a directory tree (recursive option)

Run whole Test Suite file:

mocha /path/to/test_suite.js 

Run a specific suite or test from a specific suite file.

If a suite is selected then all the child suites and/or tests will be executed.

mocha -g “Test-2” /path/to/test_suite.js 

Run a specific suite or test file by searching recursively in a directory tree.

mocha --recursive -g “Test-2” /directory/ 

For extensive CLI options:

mocha –-help 

Managing Syncing of Async Testing Code

In case async functions are used with Mocha and not handled properly, you may find yourself struggling. If asyncing code (e.g. http requests, files, selenium, etc.) is to be used in a test case, follow these guidelines to overcome unexpected results:

1. **done** Function

In your test function (it) you need to pass the done function down the callback chain — this ensures it is executed after your last step.

The example below emphasizes the done functionality. In this case three seconds of timeout will occur at the end of the test function.

it(‘Test-1’, function(done){

    setTimeout(function(){

        console.log(“timeout!”);

  // mocha will wait for done to be called before exiting function.
        done();     
    }, 3000);

});

2. Return Promise

Returning a promise is another way to ensure Mocha has executed all code lines when async functions are used (‘done’ function is not needed in this case.)

it(‘Test-1’, function(done){

    var promise;
    promise = new Promise(function(resolve, reject){
        setTimeout(function(){

            console.log("Timeout");
            resolve();

        }, 3000);

    });
    // mocha will wait for the promise to be resolved before exiting
    return promise;  
});

2. Javascript Selenium 3 Integration with MochaJS

Selenium Introduction

Selenium is a library that controls a web browser and emulates the user’s behavior. More specifically, Selenium offers specific language library APIs called ‘bindings’ for the user. These ‘bindings’ act as a client in order to perform requests to intermediate components and acting as servers in order to finally control a Browser.

The intermediate components could be the actual webdriver, found natively in each Selenium package, the selenium-standalone-server, as well as vendor native browser controlling drivers — such as Geckodriver for Mozilla, chromedriver for Chrome, etc. Moreover, Selenium webdriver communicates with browser drivers via ‘JsonWired Protocol’ and becomes a W3C Web Standard.

Selenium Installation

Before diving any deeper into Selenium integration with MochaJS, we will take a quick look into Selenium implementation with NodeJS.

In order to use the Selenium API for JavaScript (or Selenium JavaScript bindings), we should install the appropriate module:

npm install selenium-webdriver 

At this point, it should be clarified that Javascript Selenium WebDriver can also be referred to as Webdriverjs (although not in npm). Webdrivejs is different than other libs/modules, such as WebdriverIO, Protractor, etc. selenium-webdriver is the official open-source base JavaScript Selenium library while the others are wrapper libraries/frameworks that are built on top of webdriverjs API, claiming to enhance usability and maintenance.

In NodeJS code, the module is used by:

require(‘selenium-webdriver’) 

WebDriver Construction

In order to be able to use Selenium, we should build the appropriate ‘webdriver’ object which will then control our browser. Below, we can see how we use the “Builder” pattern to construct a webdriver object by chaining several functions.

Builder with Options

var webdriver = require('selenium-webdriver')
var chrome = require('selenium-webdriver/chrome'),
var firefox = require('selenium-webdriver/firefox');

var driver = new webdriver.Builder()
    .forBrowser(‘firefox’)
    .setFirefoxOptions( /* … */)
    .setChromeOptions( /* … */)
    .build();

In the code above, we have managed to build a WebDriver object which aggregates configuration for more than one browser (notice the ‘options’ methods), despite the fact that the forBrowser() method explicitly sets firefox.

The user can set the SELENIUM_BROWSER environmental variable on runtime to set the desired browser. It will override any option set by forBrowser, since we have already configured multiple browser capabilities by set Options.

The browser properties can have several types of information depending on the browser under test. For example, in Mozilla’s properties we can set the desired ‘profile’ configuration as follows:

var profile = new firefox.Profile( /* … path to firefox local profile … */);
var firefoxOptions = new firefox Options().setProfile(profile);

Then, in the above Builder snippet we can add:

‘setFirefoxOptions( firefoxOptions )’ 

Builder with Capabilities

Selenium WebDriver JavaScript API documents several ways that a webdriver could be built. One more possible way is by setting all the required driver configurations in capabilities:

var driver = new webdriver.Builder().
    .withCapabilities( { ‘browserName’ : ‘firefox’ } )
    .build();

Note that if setOptions are set after withCapabilities, the configurations will be overridden (e.g. proxy configurations).

Selenium WebDriver Control Flow and Promise Management

Since JavaScript and NodeJS are based on asynchronous principles, Selenium WebDriver behaves in a similar way. In order to avoid callback pyramids and to assist a test engineer with the scripting experience as well as code readability and maintainability, Selenium WebDriver objects incorporate a promise manager that uses a ‘ControlFlow’. ‘ControlFlow’ is a class responsible for the sequential execution of the asynchronous webdriver commands.

Practically, each command is executed on the driver object and a promise is returned. The next commands do not need to be nested in ‘thens’, unless there is a need to handle a promise resolved value as follows:

driver.get("http://www.google.com");
driver.getTitle().then(function( title ) {

    // google page title should be printed 
    console.log(title)

});

driver.quit();

Pointers for JavaScript Testing with Selenium WebDriver and Mocha

  1. driver is a webdriver object, not a promise object
  2. driver.getTitle() or driver.get(url), or any other Selenium command, returns a promise object!

This means that we can perform the following:

var titlePromise = driver.getTitle();
titlePromise.then(function(title){

    console.log(title);

});
  1. Additionally, since driver is asyncing in its base, the following will not work:
var title = driver.getTitle();
expect (title).equals("Google");

Note: title is a promise object and not an actual resolved value.

MochaJS + Selenium WebDriver

Generally speaking, Selenium WebDriver can be integrated with MochaJS since it is used in any plain NodeJS script. However, since Mocha doesn’t know when an asynchronous function has finished before a done() is called or a promise is returned, we have to be very careful with handling.

Promise Based

Selenium commands are registered automatically, to assure webdriver commands are executed in the correct sequential order a promise should be returned.

The code below shows Mocha’s (before, beforeEach, after, afterEach) or test case body it hooks.

describe( 'Test Suite' , function(){

    before(function(){

        driver.get( my_service );
        driver.findElement(webdriver.By.id(username)).sendKeys(my_username);

        // a promise is returned while ‘click’ action
        // is registered in ‘driver’ object
        return driver.findElement(webdriver.By.id(submit)).click();
    });

    after(function(){

        return driver.quit();

    });

    it( 'Test Case', function(){

        driver.getTitle().then(function(title){
            expect(title).equals(my_title);
        })

The following actions will be executed:

  1. Browser page of “my_service” is loaded
  2. Text Field with id ‘username’ is located
  3. Text Field with id ‘username’ is filled with ‘my_username’
  4. Page title is retrieved and checked for equality against ‘my_title’
  5. WebDriver quits and browser window is closed. Browser process is terminated.

Selenium Webdriver Support for MochaJS

In order to perform JavaScript testing with Selenium WebDriver and Mocha in a simple way, WebDriver facilitates usage with MochaJS by wrapping around MochaJS test functions (before, beforeEach, it, etc.) with a test object. This creates a scope that provides awareness that WebDriver is being used. Therefore, there is no need for promise returns.

First, the corresponding module should be loaded:

var test = require('selenium-webdriver/testing'); 

All the function of Mocha are preceded by ‘test.’ as follows:

test.before()
test.describe()

And so on. Then, the above code is fully re-written as:

test.describe( 'Test Suite' , function(){

    test.before(function(){

        driver.get( my_service );
        driver.findElement(webdriver.By.id(username)).sendKeys(my_username);
        driver.findElement(webdriver.By.id(submit)).click();
    });

    test.after(function(){
        driver.quit();
    });

    test.it( 'Test Case' , function(){

        driver.getTitle().then(function(title){
            expect(title).equals(my_title);
        })

        driver.sleep();
    });

});
Conclusion

In this tutorial we got a chance to experience JavaScript testing with Selenium WebDriver and MochaJS. We should keep in mind the main difference when comparing to other programming language bindings, due to the asynchronous nature of NodeJS, MochaJS and Selenium WebDriver.

As long as we keep returning promises in any function which creates a promise (either a custom test lib function or a MochaJS hook/testcase), Mocha will execute them in the correct order.

Other frameworks such as WebdriverIO, Protractor and CodeseptJS provide wrapper solutions that hide some configurations from the user, and provide some promise-enhanced handling for a better scripting experience that many test automation experts might find helpful.