The overall message of Clean Code by Robert C. Martin is that tests should be done for every class and function, and the process must be automated so that the development is carried out continuously. Writing tests in Node.js applications is a substantial process. While the code can be written in five minutes, the debugging process can take up to two hours. To avoid bugs and save time, it’s better to write unit tests for each function.
The process of writing code should be straightforward and efficient. That’s why it is so important to choose the right test tools. In this article, we’ll explore why you should avoid using Selenium for unit testing Node.js apps and explore some better options to save you time.
Jump ahead:
There is a difference in the type of testing techniques that developers use to ensure that their services work as expected. The first testing technique is unit testing, which allows you to test individual units or components of a software application in isolation without binding. Each unit should be tested individually and verified for correctness and performance with this technique.
Our second testing technique is end-to-end testing. End-to-end tests the entire system from end to end while interacting between different components or modules of a software system. While both testing types verify the behavior of a software system, they have different purposes. The purpose of end-to-end is to test the entire application from start to finish, including all the components and subsystems that make up the application. On the other hand, the purpose of unit testing is to ensure that each unit of code is functioning correctly according to its design and specification. Unit testing is usually automated and performed by developers during the development process.
Selenium is a cross-platform and cross-browser suite of tools used for automating testing. It provides point-on-click interaction methods and is used for webpage testing. Selenium was launched by Thoughtworks in 2004 and is focused on testing browser applications. Selenium is used for application testing, website performance, and web scraping. Selenium is helpful for web applications that need to be tested across multiple platforms and browsers. Here is a simple example of using Selenium in Node.js, which we’ll start by installing the following package via npm:
 npm install selenium-webdriver
After that, write a simple test script to open a webpage and check that it contains the expected text:
const { Builder, By, until } = require("selenium-webdriver"); (async function seleniumTestExample() { let driver = await new Builder().forBrowser("chrome").build(); try { await driver.get("http://localhost:8080"); await driver.wait(until.titleContains("World"), 5000); let bodyText = await driver.findElement(By.tagName("body")).getText(); if (bodyText.includes("Hi from this World")) { console.log("Passed"); } else { console.log("Failed"); } } finally { await driver.quit(); } })();
This script will open the Google Chrome browser using the Selenium Web Driver. Then, go to http://localhost:8080
, and wait for the page title, which should contain the word World
. From there, we’ll get the body text
of the page and check if the title contains the text "Hi from this World"
. If the text is found, the test will pass, and the script will return a Passed
message in the console. Otherwise, if the test fails, the console will display Failed
.
To implement unit tests in Node.js with Selenium, you need to use a testing framework such as Mocha or Jasmine and a Selenium Web Driver package like Selenium WebDriverJS. Let’s install the Mocha and Selenium Web DriverJS packages:
npm install mocha selenium-webdriver --save-dev
Now, create a new test.js
test file, and import the necessary packages:
const { Builder, By, Key, until } = require('selenium-webdriver'); const assert = require('assert');
Then, write the test cases using Mocha syntax. For example, here’s a simple test that navigates to the LogRocket website and checks if the title contains the word "LogRocket"
:
describe('LogRocket', () => { let driver; beforeEach(async () => { driver = await new Builder().forBrowser('chrome').build(); }); afterEach(async () => { await driver.quit(); }); it('should have title "LogRocket"', async () => { await driver.get('https://www.logrocket.com'); const title = await driver.getTitle(); assert(title.includes('LogRocket')); }); });
Then, run your tests using the Mocha CLI.
You have to ensure the functionality of your Node.js application using Selenium Web Driver.
Now, let’s move on to why using Selenium for unit tests in Node.js would be a problem. From the example, you noticed that Selenium is primarily intended for automated testing through the browser, and unit tests are usually focused on isolated testing of small code snippets. Because of this, Selenium is better for integration or end-to-end testing for multiple components.
Selenium tests are also much slower than other tests, which can be problematic when running unit tests that need to run fast to ensure code efficiency. The slow velocity of tests can be a problem in terms of the feedback loop between making changes to the code and getting feedback on whether those changes have introduced any issues. When tests take a long time to run, developers may be reluctant to run them frequently and skip them altogether, leading to bugs going undetected. In the case of Selenium tests, the slow speed can be attributed to the fact that they involve launching a browser and interacting with a UI, which is inherently slower than running tests that operate on code in memory.
As we acknowledged — tests with Selenium require the use of a web driver, and this introduces complex setups and additional dependencies in your testing environment. Selenium tests are often unstable, brittle, and prone to breakage due to application or test environment changes. This can make maintaining the test suite more difficult and time-consuming. In fact, debugging increases development time. Additionally, Selenium has limited support for internal testing and is primarily designed for external testing, and does not provide sufficient support for testing internal functionality.
In general, using a testing framework designed specifically for unit testing, such as Jest or Mocha, is better than using Selenium. These platforms do not require a web browser or web driver, providing a fast, easy, and more robust way to test specific individual classes and functions in isolation.
Luckily, there are more convenient and quicker alternatives for unit testing, like Mocha or Jest. Let’s look at those two alternatives in action and see how they handle unit tests. Before jumping into the comparison, let’s create a simple function that adds two numbers together as an example for tests:
function add(a, b) { return a + b; }
To test the add.js
function using Selenium, install the additional mocha
or jest
for the test environment and write something like this:
const webdriver = require('selenium-webdriver'); describe('add', () => { let driver; beforeEach(async () => { driver = new webdriver.Builder() .forBrowser('chrome') .build(); await driver.get('http://localhost:3000'); }); afterEach(async () => { await driver.quit(); }); it('should add two numbers', async () => { await driver.findElement({ name: 'a' }).sendKeys('2'); await driver.findElement({ name: 'b' }).sendKeys('3'); await driver.findElement({ name: 'submit' }).click(); const result = await driver.findElement({ id: 'result' }).getText(); expect(result).toBe('5'); }); });
The Selenium WebDriver
will launch the Chrome browser in this test, so go to the test page in localhost 3000
and complete a user input simulation to test the add.js
function. As you can see, this test sort of checks that the add
function works properly. However, it’s not a unit test because it tests the function in the context of the browser and UI — which is not correct from the point of view of the idea of unit tests.
Jest is a testing framework developed by Facebook that offers an inbuilt test runner, assertion library, and mocking support. It supports various types of testing, including snapshot testing and end-to-end testing. Let’s compare the previous example where we used Selenium with the test using Jest:
const add = require('./add'); describe('add', () => { it('should add two numbers', () => { const result = add(2, 3); expect(result).toBe(5); }); });
In this test example, we are testing the same add.js
function. Those describe
and it
are two keywords in the Jest testing framework used to define and organize test suites and test cases. In order to test the function, we have to import that function, provide values, and check the result.
In this case, we expect that the result should be 5
. Compared with Selenium, it’s shorter and faster. It’s not required to run a web driver and will not require you to use a browser at all. Jest will take only a few milliseconds to run the tests and provide feedback on the correctness of the function. In contrast, Selenium takes a significant amount of time to set up the necessary infrastructure and to write and execute this test case.
Mocha is a popular JavaScript-based testing framework for Node.js that provides a simple and flexible API for writing and running unit tests. It supports various types of testing, including asynchronous testing and browser testing. Here is an example using Mocha:
const assert = require('assert'); const add = require('./add'); describe('add', () => { it('should return the sum of two numbers', () => { const result = add(2, 3); assert.strictEqual(result, 5); }); });
This code uses the Mocha framework and the same keywords as Jest. However, we import assert
to check the result. This is an inbuilt module in Node.js that provides a set of assertion functions that can be used to write tests. But, in general, the flow is the same.
We import the function that needs to be checked and provide the strictEqual
function with the result and expectation. The time to run tests with Mocha will take a similar amount of time, similar to Jest. Mocha has a quick and efficient test runner, which makes it fast and reliable for testing small and simple functions.
Those tests will directly call the add
function and check if the result is correct. This is a true unit test because it tests the function in isolation, without any external dependencies. This approach is much faster, more reliable, easier, and cleaner. Unit tests simply perform a calculation and then check the result — taking only a fraction of a second to execute. The velocity is very important for test execution. ​​Jest and Mocha are suitable options for unit testing in Node.js applications and are significantly faster than Selenium.
Slow tests can be a barrier to efficient and effective software development, and it’s important to optimize test speed wherever possible. This may include running tests in parallel, using test data isolated from production data, and designing tests to be as focused as possible.
In the case of Selenium tests, the slow speed may be due to the fact that they involve launching a browser and interacting with the UI, which is inherently slower than running tests that work with code in memory. Because Selenium tests are usually integration or end-to-end tests, they can depend on external resources such as a database or web service, which can introduce additional latency.
Selenium is useful for testing UI and browser integration with element content in the DOM. At the same time, Jest and Mocha are better suited for true unit testing of individual code snippets in isolation. Although Selenium can be useful for testing the frontend of your web application, it is not well-suited for unit testing in Node.js.
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.