How to Use Node.js Test Runner: A Detailed Guide
Bonnie
Posted On: October 30, 2024
150764 Views
11 Min Read
The JavaScript test runner or Node.js test runner helps automate the testing process for websites and web applications by enabling a range of testing techniques, including unit, integration, and end-to-end testing.
The Node.js test runner automates running tests and offers feedback on the results to help identify and resolve bugs during the stages of software development effectively.
In this blog, we look at how to leverage Node.js test runner for automated testing while exploring methods such as mocking and parallel testing alongside test hooks.
TABLE OF CONTENTS
What Is Node.js Test Runner?
Programming languages like Python, Ruby, and Go had a built-in test runner, but JavaScript did not have one. All test runners in the JavaScript ecosystem, such as Mocha, Jest, and Jasmine, were built as third-party packages.
All this changed when Node.js released an experimental built-in test runner in Node.js version 18 and made the test runner stable in Node.js version 20.
The test runner offers several features, such as:
- Assertion library
- Test hooks
- Mocking functionality
- Code coverage
- Test reporters
- Asynchronous testing
Why Use Node.js Test Runner?
The native test runner in Node.js provides several advantages for JavaScript automation testing. Outlined below are some of the benefits of utilizing this test runner:
- The test runner streamlines the testing process for developers and testers by saving time in deciding which tool to use, as it is integrated within Node.js itself.
- The built-in assertion library in the Node.js test runner simplifies the process of writing tests and obtaining test results without the need to install other assertion libraries.
- The test runner includes a feature for code coverage to make sure that all sections of the code are tested by pinpointing the parts that have not been tested yet.
- The native test runner has a feature called watch mode that allows it to monitor changes in test files and their dependencies. If any changes are detected, the test runner will automatically rerun the tests affected by the modification.
- Testing using Node.js enables the use of mocks, stubs, and spies that are important for testing individual components in isolation from the entire software.
Run Node.js tests across 3000+ real environments. Try LambdaTest Today!
How to Get Started With Node.js Test Runner?
In this part of the blog post, you will learn how to start using the Node.js built-in test runner and run your testing script successfully.
To get started, follow these instructions;
- Make sure to use Node.js version 20 or above because the test runner has been completely integrated into the core of Node.js since the release of version 20.
- Create a TestRunner directory and launch it using your Integrated Development Environment (IDE). For instance, in this scenario, we have used VS Code.
- Run the following command in the terminal to set up a Node.js project:
1npm init -y - Create two new folders named src and tests in the TestRunner directory.
- Update the test script in your package.json file with the following code snippet:
123"scripts": {"test": "node --test TestRunner/tests/"},
- Install Selenium WebDriver by running the command below:
1npm install selenium-webdriver
- Run the below-given command to install ChromeDriver:
1npm install chromedriver
- Create a new started.test.js file in the tests folder. Then, add the following code to the file:
12345678910111213141516171819202122232425const { Builder, By, Key, until } = require("selenium-webdriver");const assert = require("assert").strict;const { test } = require("node:test");{let driver;test("Setup WebDriver for Google Search Test Suite", async (t) => {driver = await new Builder().forBrowser("chrome").build();});test("Navigate to Google and verify title", async (t) => {await driver.get("http://www.google.com");const title = await driver.getTitle();assert.strictEqual(title, "Google");});test("Cleanup after Google Search Test Suite", async (t) => {await driver.quit();});}
The above code imports the required functions from node:test and node:assert and describes a simple test that navigates to Google and verifies the title.
- Run the below-given command to execute the test:
1node tests/started.test.js
Using describe() and it() Blocks
In Node.js test runner, you can use describe() and it() blocks to run tests. The describe() block is used to declare a suite that organizes and groups related tests together, while the it() block is used to declare a test.
The benefit of using describe()/it() blocks is that it provides a way to organize your tests into blocks of related functionality or features. This is useful for larger test suites, where you want to keep tests neatly organized and logically grouped.
Inside a describe() block, you can have multiple test() or it() blocks that define specific test cases. You can also nest describe() blocks within each other to create sub-groups of tests for more detailed organization.
You can write tests using describe() and it() blocks, as shown below.
- Create a describeit.test.js file within the tests directory and insert the provided code snippet below:
12345678910111213141516171819202122232425262728describe("Form Input Test", async () => {let driver;await it("Setup WebDriver", async () => {driver = await new Builder().forBrowser("chrome").build();});await it("should input values in a form and check their sum", async () => {await driver.get("https://www.lambdatest.com/selenium-playground/simple-form-demo");await driver.findElement(By.id("sum1")).sendKeys(2);await driver.findElement(By.id("sum2")).sendKeys(3);await driver.findElement(By.xpath("//button[normalize-space()='Get Sum']")).click();let sum = await driver.wait(until.elementLocated(By.id("addmessage")),10000);let sumNo = await sum.getText();assert.strictEqual(sumNo, "5");});await it("Cleanup: Close the browser", async () => {await driver.quit();});});
- Run the below-given command to execute the test:
1node tests/describeit.test.js
Skipping Tests
The Node.js test runner also allows you to skip tests. You can skip tests if they are flaky, the feature being tested is under active development, the test is dependent on unavailable external dependencies, or tests are on deprecated features.
Individual tests can be skipped by passing the skip option to the test or by calling the test context’s skip() annotation. Annotations to avoid running the test in the built-in test runner consist of indicators, like skip:true, skip;’This test is skipped’, t.skip(), and t.skip(“This test is skipped”) as illustrated in this example.
- Create a skipping.test.js file within the tests directory and insert the provided code snippet below.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748describe("Expected Values To Be Strictly Equal", async () => {let driver;driver = await new Builder().forBrowser("chrome").build();it("should be strictly equal", async () => {await driver.get("http://www.google.com");const title = await driver.getTitle();assert.strictEqual(title, "Google");});it("skip test option", { skip: true }, async (t) => {await driver.get("http://www.google.com");const title = await driver.getTitle();assert.strictEqual(title, "Google");});it("skip test option with message",{ skip: "This test is skipped" },async (t) => {await driver.get("http://www.google.com");const title = await driver.getTitle();assert.strictEqual(title, "Google");});it("Calling skip() method", async (t) => {await driver.get("http://www.google.com");const title = await driver.getTitle();assert.strictEqual(title, "Google");t.skip();});it("Calling skip() method with message", async (t) => {await driver.get("http://www.google.com");const title = await driver.getTitle();assert.strictEqual(title, "Google");t.skip("This test is skipped");});await it("Cleanup: Close the browser", async () => {await driver.quit();});});
- Run the below-given command to execute the test:
1node tests/skipping.test.js
Using Test Hooks
The Node.js test runner provides different test hooks. Hooks are functions that run immediately before or immediately after a test. The hooks available in the Node.js test runner are before(), beforeEach(), after(), and afterEach().
Here are some examples of how to use these hooks:
before() Hook
The before() hook is used to prepare the testing environment where it runs once before all the tests in a describe block. For example, you can use the before() hook to set up the WebDriver before all your tests are executed.
Below is how to use the before() hook:
- Create a beforehook.test.js file within the tests directory and insert the provided code snippet below:
1234567891011121314151617181920describe("Simple Form Demo Title Test", async () => {let driver;before(async () => {driver = await new Builder().forBrowser("chrome").build();});await it("should Navigate to Simple Form Demo and verify title", async () => {await driver.get("http://www.lambdatest.com/selenium-playground/simple-form-demo");const title = await driver.getTitle();assert.strictEqual(title, "Selenium Grid Online | Run Selenium Test On Cloud");});await it("Cleanup: Close the browser", async () => {await driver.quit();});});
- Run the below-given command to execute the test:
1node tests/beforehook.test.js
beforeEach() Hook
The beforeEach() hook runs once before each test, where it is used to isolate tests so they do not affect each other. For example, if you have to visit a specific page URL for a couple of tests, you can use a beforeEach() hook to open the URL page before each test is executed.
Below is how to use the beforeEach() hook:
- Create a beforeEachhook.test.js file within the tests directory and insert the provided code snippet below:
123456789101112131415161718192021222324252627282930313233343536describe("Simple Form Demo Test", async () => {let driver;before(async () => {driver = await new Builder().forBrowser("chrome").build();});beforeEach(async () => {await driver.get("https://www.lambdatest.com/selenium-playground/simple-form-demo");});await it("should Navigate to Simple Form Demo and verify title", async () => {const title = await driver.getTitle();assert.strictEqual(title,"Selenium Grid Online | Run Selenium Test On Cloud");});await it("Should add values to simple form input fields and check their sum", async () => {await driver.findElement(By.id("sum1")).sendKeys(2);await driver.findElement(By.id("sum2")).sendKeys(3);await driver.findElement(By.xpath("//button[normalize-space()='Get Sum']")).click();let sum = await driver.wait(until.elementLocated(By.id("addmessage")),10000);let sumNo = await sum.getText();assert.strictEqual(sumNo, "5");});await it("Cleanup: Close the browser", async () => {await driver.quit();});})
- Run the below-given command to execute the test:
1node tests/beforeEachhook.test.js
after() Hook
The after() hook runs once after the execution of all the tests where it is used to perform cleanup actions after all the tests are executed. For example, if you want to close the WebDriver after the tests have been executed, you can use the after() hook.
Below is how to use the after() hook:
- Create a afterhook.test.js file within the tests directory and insert the provided code snippet below:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546describe("Simple Form Demo Test", async () => {let driver;before(async () => {driver = await new Builder().forBrowser("chrome").build();});beforeEach(async () => {await driver.get("https://www.lambdatest.com/selenium-playground/simple-form-demo");});await it("should Navigate to Simple Form Demo and verify title", async () => {const title = await driver.getTitle();assert.strictEqual(title,"Selenium Grid Online | Run Selenium Test On Cloud");});await it("Should add values to simple form input fields and check their sum", async () => {await driver.findElement(By.id("sum1")).sendKeys(2);await driver.findElement(By.id("sum2")).sendKeys(3);await driver.findElement(By.xpath("//button[normalize-space()='Get Sum']")).click();let sum = await driver.wait(until.elementLocated(By.id("addmessage")),10000);let sumNo = await sum.getText();assert.strictEqual(sumNo, "5");});after(async () => {await driver.quit();});});
- Run the below-given command to execute the test:
1node tests/afterhook.test.js
afterEach() Hook
The afterEach() hook is run once after each test. It is used to perform cleanup actions after each test. For example, if you want to clear cookies after each test, you can use an afterEach() hook.
- Create a afterEachhook.test.js file within the tests directory and insert the provided code snippet below:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263const { Builder, By, Key, until } = require("selenium-webdriver");const assert = require("assert").strict;const {describe,it,before,beforeEach,after,afterEach,} = require("node:test");describe("Simple Form Demo Test", async () => {let driver;before(async () => {driver = await new Builder().forBrowser("chrome").build();});beforeEach(async () => {await driver.get("https://www.lambdatest.com/selenium-playground/simple-form-demo");});await it("should Navigate to Simple Form Demo and verify title", async () => {const title = await driver.getTitle();assert.strictEqual(title,"Selenium Grid Online | Run Selenium Test On Cloud");});await it("Should add values to simple form input fields and check their sum", async () => {await driver.findElement(By.id("sum1")).sendKeys(2);await driver.findElement(By.id("sum2")).sendKeys(3);await driver.findElement(By.xpath("//button[normalize-space()='Get Sum']")).click();let sum = await driver.wait(until.elementLocated(By.id("addmessage")),10000);let sumNo = await sum.getText();assert.strictEqual(sumNo, "5");});afterEach(function () {driver.manage().deleteAllCookies();});after(async () => {await driver.quit();});});
- Run the below-given command to execute the test:
1node tests/afterEachhook.test.js
Implementing Mocks
The built-in mocking functionality of the Node.js test runner enables you to mock and substitute functions during testing situations where external dependencies or third-party packages are being used. It is particularly handy when those dependencies are still in the development phase.
You can use mocking functionality to create spies and stubs. Below is an example that illustrates using mocking functionalities to validate fetching data from an API:
First, install axios, a promise-based HTTP client for the browser and Node.js, using the below-given command:
1 |
npm install axios |
Then, create an index.js file and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const axios = require("axios"); class MakeRequest { constructor() {} static async fetchDataFromAPI(id) { const { data: todo } = await axios.get( "https://jsonplaceholder.typicode.com/todos/1" ); return todo; } static slugifyTitle(todo) { const slug = `${todo.title.replace(/ /g, "-")}${todo.id}`; return { ...todo, slug }; } static async addToDB() { let todo = await this.fetchDataFromAPI(); todo = this.slugifyTitle(todo); return todo; } } module.exports = MakeRequest; |
The code above implements a MakeRequest class, which has three functions fetchDataFromAPI(), slugifyTitle(), and addToDB().
Then, create a mock.test.js file and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// Describing the tests related to mocking describe("Mocking Tests", async () => { // Resetting mocks before each test beforeEach(() => mock.restoreAll()); // Testing fetchDataFromAPI function to return a product it("fetchDataFromAPI should return a product", async (t) => { // Mocking the fetchDataFromAPI function to return a predefined product mock .method(MakeRequest, MakeRequest.fetchDataFromAPI.name) .mock.mockImplementation(async () => { return { userId: 1, id: 1, title: "delectus aut autem", completed: false, }; }); // Calling the function and asserting the returned product properties const res = await MakeRequest.fetchDataFromAPI(); assert.strictEqual(res.userId, 1); assert.strictEqual(res.completed, false); }); // Testing fetchDataFromAPI function to return a product with a given ID it("fetchDataFromAPI should return product with given ID", async (t) => { const id = 3; // Mocking the fetchDataFromAPI function to return a product with a given ID mock .method(MakeRequest, MakeRequest.fetchDataFromAPI.name) .mock.mockImplementation(async (id) => { return { userId: 1, id: id, title: "delectus aut autem", completed: false, }; }); // Calling the function with the provided ID and asserting the returned ID const res = await MakeRequest.fetchDataFromAPI(id); assert.deepEqual(res.id, id); }); // Testing addToDB function to create a slug based on the title it("should create a slug based on the title", async (t) => { // Spy on the slugifyTitle method const slugSpy = mock.method(MakeRequest, "slugifyTitle"); // Mocking the fetchDataFromAPI function to return a predefined product mock .method(MakeRequest, "fetchDataFromAPI") .mock.mockImplementation(async () => { return { userId: 1, id: 1, title: "delectus aut autem", completed: false, }; }); // Calling the addToDB function await MakeRequest.addToDB(); // Getting the call information for slugifyTitle method const call = MakeRequest.slugifyTitle.mock.calls[0]; // Asserting the number of calls made to slugifyTitle and fetchDataFromAPI assert.deepEqual(slugSpy.mock.calls.length, 1); assert.deepEqual(MakeRequest.fetchDataFromAPI.mock.callCount(), 1); // Asserting the created slug based on the title assert.strictEqual(call.result.slug, `delectus-aut-autem1`); }); }); |
In the code above, the fetchDataFromAPI method is mocked from the MakeRequest class.
To prevent the function from making a network request, the mockImplementation() method is used to return a predefined output, which can be tested for specific values.
Finally, mock.method() is used to create a spy to test if the slugifyTitle() function is called. Also, how many times the function is called, and its output is tested based on the title.
Run the tests using the below-given command:
1 |
node tests/mock.test.js |
Running Parallel Tests
Node.js test runner allows you to execute multiple tests in parallel and at the same time instead of running them sequentially.
To run tests in parallel in Node.js test runner, you need to pass the concurrency: true parameter as the second argument to the describe() function.
Below is an example of how to run tests in parallel using Node.js native test runner and Selenium using concurrency parameters.
- Create a parallel.test.js file in the tests folder and add the following code:
123456789101112131415161718192021222324252627282930describe("Ecommerce Site Tests", { concurrency: true }, async () => {let driver;before(async () => {driver = await new Builder().forBrowser("chrome").build();await driver.get("https://ecommerce-playground.lambdatest.io/");});await it("should Navigate to ecommerce site and find an item", async () => {const title = await driver.getTitle();assert.strictEqual(title, "Your Store");});await it("should Navigate to ecommerce site and add item to cart", async () => {driver.findElement(By.xpath('//a[normalize-space()="Shop by Category"]')).click();driver.findElement(By.xpath('//span[normalize-space()="Phone, Tablets & Ipod"]')).click();driver.findElement(By.xpath('//div[@class="carousel-item active"]//img[@title="iPhone"]')).click();});afterEach(function () {driver.manage().deleteAllCookies();});after(async () => {await driver.quit();});});
- Run the below-given command to run the test:
1node tests/parallel.test.js
The above test executions are performed on the local grid. However, to scale your automation testing with Node.js, you can consider using a cloud-based testing approach.
AI-driven test execution platforms like LambdaTest let you run Node.js tests on a scalable automation cloud infrastructure, ensuring compatibility and reliability.
Check out this guide to get started with JavaScript automation with LambdaTest.
Conclusion
In summary, the Node.js test runner offers a lightweight solution for creating and executing automated tests in your web projects. Though it may not include all the functionalities found in popular testing frameworks, its straightforwardness and user-friendly nature make it an excellent option for beginning automated testing.
This blog discusses the features of the Node.js built-in test runner. It covers how to create tests using the describe() function and its syntax while making use of hooks for setup and teardown operations, as well as mocking and running tests in parallel threads concurrently for JavaScript code quality assurance and stability enhancement purposes.
For complex testing situations, you may want to evaluate the default test runner against popular frameworks such as Mocha, Jasmine, or Jest to determine which one best suits your requirements.
Frequently Asked Questions (FAQs)
What is a test runner in JS?
A test runner in JavaScript is a framework that automatically runs tests, tracks the results, and provides reports on the success or failure of those tests.
What is a test runner?
A test runner is a framework that executes automated tests, manages their lifecycle, and reports the outcomes for easier debugging.
What is the purpose of the Node.js server test runner in Cypress?
The Node.js server test runner in Cypress manages test execution, controlling the browser, and handling test logic while interacting with your web application.
Got Questions? Drop them on LambdaTest Community. Visit now