CHAPTERS
OVERVIEW
In the current world of software development, developers are required to ship software of high quality faster. Shipping or releasing software frequently and within shorter time frames might lead to code with bugs and errors discovered later in development.
Discovering bugs and errors later will take a lot of time and effort to solve the errors. Also, after fixing each bug, the code becomes complex, which reduces software performance.
You can release bug and error-free software faster by making unit testing a mandatory part of your development process. You can run automated unit tests on your code to discover bugs and errors at an earlier stage.
However, a wide range of unit test automation frameworks are available in the market based on different programming languages. That means deciding the right test automation framework for unit testing can be daunting.
Since JavaScript is one of the most popular programming languages, you can use a range of automated unit testing frameworks it offers. JavaScript automated unit testing frameworks include Jest, Mocha, and Jasmine.
In this Jest tutorial on unit testing with Jest, you will learn what unit testing is and how to run unit tests on JavaScript code using the Jest unit testing framework.
So, let’s get started!
Jest is a JavaScript test automation framework designed to ensure that the JavaScript codebase is bug-free and works as expected. Jest is built on top of Jasmine (an open-source testing framework for JavaScript) and maintained by Meta.
Jest framework was designed to focus on simplicity and support for large web applications. It works with projects using Babel, TypeScript, Node.js, React, Angular, Vue.js, and Svelte.
Jest is a prevalent unit testing framework, and the data obtained from the official Jest GitHub repository says it all.
At the time of writing this unit testing with Jest, the Jest testing framework had 19,636,616 downloads on npmjs.
Besides unit testing with Jest, you can also use Jest to perform end-to-end testing on your application.
Since there are other types of JavaScript automated unit testing frameworks, here are the advantages of using unit testing with Jest framework.
Jest is config free and works out of the box on most JavaScript projects unless you use some syntax not supported by Node out of the box, such as JSX, TypeScript, and Vue templates.
Jest supports the execution of Smart Visual Regression Tests by capturing screenshots (i.e., Snapshot testing.)
Jest enables you to isolate unit tests in a sandboxed environment where the side effects of running one test do not impact other tests.
Jest has the entire toolkit of its API in one place where it is well documented and maintained.
Jest can reliably run tests in parallel by ensuring your tests have a unique global state. Jest uses a fail-fast approach, runs previously failed tests first, and re-organizes runs based on how long test files take to make things quick.
Jest can collect code coverage information from entire projects, including untested files.
Jest can easily mock any dependency imported in the module that must be tested.
Mocking is a technique to isolate test code by replacing dependencies with objects you can control and inspect. A dependency can be anything your code depends on, but it is typically a module that the code imports.
In JavaScript, great mocking libraries are available, and Jest provides mocking out of the box.
Perform Jest testing at scale on 3000+ browsers and browser versions. Try LambdaTest Now!
Unit testing with Jest is a software testing process where units, the smallest parts of code in an application, are tested to ensure that they work as expected. In JavaScript, unit testing with Jest allows you to test small, self-contained units of JavaScript code which are part of a web page or web application.
The advantages of unit testing with Jest framework are:
As unit testing scales, using unit testing frameworks becomes important. Automated unit testing with Jest framework can help you create exhaustive test cases and execute them automatically as a tester. Unit testing with Jest framework provides everything you need to build, execute, and report on unit tests.
Apart from unit testing with Jest framework being useful for development stages, they can also be useful for debugging and testing stages of a software application.
Unit testing follows a divide-and-conquer approach. Its main goal is to divide each part of the AUT and ensure that each unit works as expected. In this section of the Jest tutorial, we have covered some of the major benefits of unit testing:
This is one of the most beneficial factors of unit testing. Projects with big and bulky features often require older implementation and design modification. This is because product development evolves! However, making these changes (without keeping track of the dependencies) can prove fatal and may break the whole functionality. If we have separate units allocated, we can easily proceed with the refactoring and further testing.
Unit testing goes side-by-side with the agile programming process, making the coding process and refactoring much easier.
Unit testing allows you to modify or update code at any point with much fewer chances of breaking the functionality. It allows you to detect changes and defects early, which helps simplify changes in the project.
Unit testing also ensures the proper working of each unit independently. Successful testing of these units simplifies the integration of the AUT's complete functionality. At a later point in time, testing of the AUT during the integration process gets simplified due to the successful testing of individual units.
Unit testing helps find issues at an early stage. As the developers often carry out unit testing during the initial phases, identifying and resolving the bugs becomes very easy. These bugs may include both code flaws and errors in specification.
Since it is carried out during the initial phases, i.e., before the code is sent for integration testing, unit testing explores all the edge cases. Unit testing helps maintain the code quality by modifying it before it is deployed to upper environments.
Debugging is fairly simplified in unit testing. Since the AUT is divided into units and each unit is tested separately, any failed test only requires debugging the implementation in the respective unit (or module) under test.
Imagine finding bugs during the final stages of development. How would that impact your project costs? Since unit testing is carried out during the initial phases, bugs are caught early, reducing time and money.
Unit testing alone is not enough to test the working and durability of your application, but it provides an important base for building a formidable testing strategy. Now that we have walked through unit testing briefly, let us move on to the Jest framework.
In this tutorial, we will dive deep into performing unit testing with Jest.
In this blog section of this tutorial on unit testing with Jest, you will learn how to install the Jest framework and run your first test. Jest is a node package and can be installed using any node-based package manager, such as npm and yarn.
To install and use the Jest framework system, you need
For the demonstration, I will be using VS code as my text editor, but you can use the IDE of your choice.
To install the Jest framework, follow the steps below.
Step 1: Install Jest globally using the -g flag in an npm command. This enables you to use Jest commands without configuring the package file for npm tests.
On your system command line, run the following command.
npm install -g jest
The screenshot below shows that Jest was globally installed in my system.
Once the installation is complete, run the command below to check the Jest version installed.
npm install -g jest
As you can see from the screenshot below, version 29.0.3 of the Jest framework has been installed in my system.
Step 2: Run the command below on the terminal line to create a project folder called “JestUnitTesting.”
mkdir UnitTesting
Then run the command below to make the “JestUnitTesting” folder the current directory.
cd UnitTesting
Step 3: Run the command below on the command line to open the “JestUnitTesting” project folder on Visual Studio Code.
code UnitTesting
Step 4: Run the command below on the command line to install Jest in your project. Adding --save-dev to the command helps save the name and version of Jest in the dev-dependency object.
npm install --save-dev jest
Once the installation is complete, you will realize a node_modules package, package-lock.json, and package.json files have been added to the project folder UnitTesting, as shown below.
Step 5: Configure the npm test script to run the Jest tests when the command npm test is executed. To do that, update the package.json file and add a script section, as shown below.
"scripts": {
"test": "jest"
}
The package.json file code should now look as shown below.
{
"name": "jestunittesting",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"jest": "^29.0.3"
}
}
Let us assume you have a web application that requires users to create or register for an account to use the web app. You have been writing JavaScript code for the registration form like the one on this CodePen by Mamikon.
After writing the code, you want to ensure the user enters the right information on the input fields, such as Name, Email, and Password. You can ensure the registration form JavaScript code works as expected by following the steps below, where we will write the corresponding Jest-based tests.
Step 1: Create a file called registration.js in the UnitTesting project folder, as shown below.
Step 2: Add the registration form JavaScript code below to your registration.js file.
FilePath - UnitTesting/registration.js
regForm = function(){
let form = document.main_form;
let info = document.getElementById("info");
let name = form.name;
let lastname = form.lastname;
let surname = form.surname;
let gender = form.gender;
let date = form.birthday;
let email = form.email;
let password = form.password;
let confirmPassword = form.confirmPassword;
form.addEventListener("submit", formValidation);
function formValidation(){
let wrongFields = false;
for(let i=0; i<form.length; i++){
if(form[i].value == "" && form[i].type != 'radio'){
form[i].classList.add("not-valid");
wrongFields = true;
}
else
form[i].classList.remove("not-valid");
}
if(form.password.value != form.confirmPassword.value)
{
info.innerHTML = "Passwords is not same";
event.preventDefault();
}
else
info.innerHTML = "";
if(!wrongFields)
alert("Thanks for registration")
else
event.preventDefault();
}
for(let i=0; i<form.length; i++){
form[i].onchange = function(){
if(this.value !="")
this.classList.remove("not-valid");
}
}
}
module.exports = formValidation
In the registration form code above, we have a Window.onload event that ensures the registration page is completely loaded before launching the registration function.
Inside the Window.onload event function, form and form input fields variables are declared.
JavaScript event handler in the addEventListener() method is added to the form where it is attached to the submit event that is fired before the request is sent to the server when submitting the form. Inside the method, the formValidation function that validates the form is called.
The formValidation function that validates data entered in the form fields is created.
Inside the formValidation function, a variable called wrongFields that is set to false is declared.
Next is a for loop with initialExpression of variable i set to 0, which increases by one as long as the loop code block condition is true. The loop has a condition of continuing to execute the block of code inside the loop as long as the value of variable i is less than the length or number of form inputs.
Inside the for loop is an if statement with the condition that checks if the form input fields are empty as long as they are not of type radio. If the input field is empty, a class name called “not-valid” is added to the HTML of the registration page. Also, the variable wrongFields is set to true.
If the input field is not empty, the class name “not-valid” added to HTML is removed.
Next is an if statement with the condition that checks if the password input value is not equal to the confirmPassword input value. If both values are not the same, a message of “Passwords is not same” is displayed. If both values are the same, no message is displayed.
Next, an if statement with the condition that checks if variable wrongFields is set to true. If the variable is true, an alert that reads “Thanks for registration” is displayed. If the variable is not set to true, the preventDefault() method prevents the submit button from submitting the form.
The formValidation function should now look as shown below.
After the formValidation function is a for loop that removes the class name “not-valid” as long as the form input values are not empty when a change occurs.
At the bottom of the file, module.exports object is used to export values of the formValidation() function.
Your registration.js file should now look as shown below.
Step 3: Create a folder called tests in the UnitTesting project folder. Create a test file called registration.test.js inside the tests folder, as shown below.
The Jest framework expects the filename.test.js naming convention to find all the files that contain Jest-based tests.
Step 4: Let us now import formValidation() function values from the registration.js file to the registration.test.js file using the require function as shown below.
const regForm = require('./../registration');
Node.js follows the commonJS module system where the built-in require function is the easiest way to include modules that exist in separate files.
Step 5: Let us now write tests in the registration.test.js file that checks if the registration form JavaScript code validates the form fields as expected. Note that the Jest framework follows Behavior Driven Development (BDD) testing practice where each test suite has one main describe block, which can have multiple test blocks.
The code on the registration.test.js file should now look as shown below.
const regForm = require('./../registration');
describe("Registration Form Validation Test", () => {
test('Check if input is not empty string', () => {
const formValue = {
name: 'John',
lastName: 'Doe',
birthday: '14/05/1999',
email: 'john@example.com',
password: '123456',
confirmPassword: '123456'
};
expect(typeof formValue.name).toBe("string");
expect(typeof formValue.lastName).toBe("string");
expect(typeof formValue.birthday).toBe("string");
expect(typeof formValue.email).toBe("string");
expect(typeof formValue.password).toBe("string");
expect(typeof formValue.confirmPassword).toBe("string");
});
test ('Check if password and confirmPassword values march', () => {
const marchingPass = {
password: '123456',
confirmPassword: '123456'
};
expect(correctPass.password).toEqual(correctPass.confirmPassword);
});
})
From the code above, the describe block is an outer description for the test suite, where it represents a generic container for all the tests that we have written for the registration form. Next, we have two test blocks, each representing a single test where the string in quotes represents the name of the tests.
The first test block has a JavaScript object formValue with input fields as properties and input field values as property values. Then expect function tests whether each form input value is a string to ensure it is not empty.
The second test has a JavaScript object marchingPass with input fields password and confirmPassword and their values. Then expect function tests whether the password input value equals the confirmPassword input value.
Step 5: To run the tests, run the command below on the command line.
npm test
Once the command is done running, you should be able to see the results of the tests on the command line, as shown below.
In the above section of this tutorial on unit testing with Jest, you learned that when writing tests in Jest, a describe block is used to organize test cases in logical groups of tests. Inside the describe block is a test block representing a single test. Inside the test block is an expect function, as shown below.
describe("Calculator Functionality Tests", () => {
test('adding 2 + 5 should return 7', () => {
expect(calculator.addition(2, 5)).toBe(7);
});
})
Expect function gives you access to a number of matchers that let you validate that values in your code meet certain conditions when writing tests. From the code sample above, the .toBe() matcher is used to ensure that the expected result of 2 + 5 is 7.
Commonly used matcher types are:
Common matchers, also known as equality matchers, are the most commonly used matchers, as the name suggests. These matchers are used to check whether values are equal or not equal and are mostly used for arithmetic operations.
There are two common matchers, which are .not.toBe() matcher is used to test whether a value is equal to a condition while not.toBe() matcher is used to test whether a value is not equal to a condition.
Here is how the .toBe() matcher is used to test whether a value is an exact equality.
test('Three plus two is five', () => {
expect(3 + 2).toBe(5);
});
Here is how .not.toBe() matcher is used to test whether a value is not equal to a certain condition.
test('Adding Three and two is not 6', () => {
expect(3 + 2).not.toBe(6);
});
Truthiness matchers enable you to distinguish between undefined, null, and false without treating them differently. These matchers are such as, .toBeNull(), .not.toBeNull() .toBeDefined(), .toBeUndefined(), .toBeTruthy() and .toBeFalsy().
The .toBeNull() matcher matches only null values, as shown below.
test('Null Value', () => {
const a = null
expect(a).toBeNull();
});s
The .not.toBeNull() matcher matches values that are not null, as shown below.
test('Values not null', () => {
const a = 10
expect(a).not.toBeNull();
});
The .toBeDefined() matcher matches values that are defined as shown below.
test('Defined Value', () => {
const a = 10
expect(a).toBeDefined();
});
The .toBeUndefined() matcher matches values that are undefined as shown below.
test('Undefined Value', () => {
var a;
expect(a).toBeUnDefined();
});
The .toBeTruthy() matcher matches anything that an if statement treats as true. The example below shows that var a has a valid value.
test('Has a valid Value', () => {
var a = 5;
expect(a).toBeTruthy();
});
The .toBeFalsy() matcher matches anything that an if statement treats as false. The example below shows that var n is false because null values are treated as false or negative.
test('Has a false Value', () => {
var n = null;
expect(n).toBeFalsy();
});
Number matchers are mostly used for general arithmetic operations. These matchers are such as .toBeGreaterThan(), .toBeGreaterThanOrEqual(), .toBeLessThan(), and .toBeLessThanOrEqual().
The .toBeGreaterThan() matcher matches whether a value is greater than a given value, as shown below
test('Value is greater than or equal', () => {
var num = 18;
expect(num).toBeGreaterThanOrEqual(15);
});
The .toBeGreaterThan() matcher matches whether a value is greater than a given value, as shown below
test('Value is greater than or equal', () => {
var num = 18;
expect(num).toBeGreaterThanOrEqual(15);
});
The .toBeLessThan() matcher matches whether a value is less than a given value, as shown below
test('Value is less than', () => {
var num = 18;
expect(num).toBeLessThan(20);
});
The .toBeLessThanOrEqual() matcher matches whether a value is less than or equal to a given value as shown below.
test('Value is less than or equal', () => {
var num = 18;
expect(num).toBeLessThanOrEqual(19);
});
String matchers are used to match strings against a regular expression. These matchers are such as .toMatch() and .not.toMatch(). The .toMatch() matcher matches whether a string matches anything in a regular expression, as shown below.
test('String matches a regular expression', () => {
var text = "Writing unit tests is quite exciting.";
expect(text).toMatch(/unit/);
});
The .not.toMatch() matcher matches whether a string does not match anything in a regular expression, as shown below.
test('String does not match a regular expression', () => {
var text = "Writing unit tests is quite exciting.";
expect(text).not.toMatch(/Jest/);
});
Arrays and iterables matchers are used to check if an array or iterable contains a particular item using the .toContain() matcher. The example below shows how to use the .toContain() matcher to check if the car array has a Tesla item.
const Cars = [
'Toyota',
'Mercedes',
'Tesla',
'Audi',
'Honda',
];
test('the car list has Tesla on it', () => {
expect(Cars).toContain('Tesla');
});
Code coverage is a software testing metric that determines the percentage of lines of code that have been tested compared to the total number of lines being tested. Code coverage makes it possible to spot untested lines in our code.
Jest enables you to generate a code coverage report by adding the flag --coverage to the npm test command. No additional setup is needed, and Jest can collect code coverage reports from entire projects, including untested files.
Step 1:Let us now generate a code coverage report for the calculator code we tested in an earlier section above. To generate the code coverage report, run the below command on the command line.
npm test -- --coverage
Once the command completes running, you should be able to see the calculator code coverage report on the command line, as shown below.
Step 2: Open VS Code, and you will discover that a new folder called coverage has been created, as shown below.
Step 3: Inside the coverage folder, open the Icov-report folder and then open the HTML file as shown below.
Step 4: Open the HTML file on the browser, and you should see the code coverage report as shown below.
JavaScript code can have several dependencies, which are internal or external. The dependencies are represented by a list of imports at the top of the JavaScript code file.
Inputs, outputs, and invocations of these dependencies must be controlled to write repeatable unit tests. Dependencies that can be mocked are such as.
Mock functions, also known as “spies,” let you spy on the behavior of a function that is called indirectly by some other code rather than only testing the output.
You can create a mock function with jest.fn(), and if no implementation is given, the mock function will return undefined when invoked.
When a web application is live or in production, you don’t have a choice on which browsers users can use to access the web app. It is not a good idea to require users to use a specific browser to access your web application.
However, how can you ensure your web application works as expected on all these browsers without spending a fortune on the infrastructure needed to run the tests? Use LambdaTest continuous quality cloud testing platform that lets you perform automation testing with Jest and Selenium on a cloud Selenium Grid across 3000+ different browsers and operating systems.
Subscribe to the LambdaTest YouTube channel when you want to catch up with the latest news on automated browser testing, automated UI testing, etc.
Since we have run unit tests on the registration form JavaScript code to ensure it works as expected, let us now test whether it works as expected when accessed through different browsers.
The advantage of using cloud Selenium Grid is that you can test a web app on multiple browser versions and operating systems. In this case, we can use three test combinations to test whether an e-commerce website registration form works as expected. These test combinations are:
To define your desired browser and operating system combinations; you can use LambdaTest Capabilities Generator.
Generating browser and operating system combinations.
Step 1: On the capabilities generator page, select the programming language you are using to run your tests. In this case, we are using JavaScript and can select Node JS, a JavaScript framework.
Step 2: Configure your capabilities by selecting a browser and browser version.
Step 3: Click Configure Advanced Capabilities and select the operating system.
You can now see the capabilities presented in a code format that you can copy and use in your test script.
For our test case, below are capabilities for browser and operating system combinations we want to test an e-commerce website registration form on.
Creating and Running Test Script
Step 1: In the UnitTesting project folder created in the Unit Testing section, create a file called regform.test.js in the tests folder, as shown below.
Step 2: Install the latest version of the Selenium WebDriver in the project folder by running the command below on the command line.
npm install selenium-webdriver
Your terminal line should look as shown below once the webdriver is installed.
Then run the npm command below on the command line to install a Node.js JavaScript client for working with LambdaTest through Automation API.
npm i @lambdatest/node-rest-client
Your terminal line should look as shown below once the client is installed.
Step 3: In the regform.test.js file, add the following test automation script.
const webdriver = require('selenium-webdriver');
const { until } = require('selenium-webdriver');
const { By } = require('selenium-webdriver');
const LambdaTestRestClient = require('@lambdatest/node-rest-client');
const assert = require('assert');
const username = process.env.LT_USERNAME || 'Username';
const accessKey = process.env.LT_ACCESS_KEY || 'Access Key';
const AutomationClient = LambdaTestRestClient.AutomationClient({
username,
accessKey
});
const chromeWindowsCapability = {
"browserName": "Chrome",
"browserVersion": "105.0",
"LT:Options": {
"username": "Username",
"accessKey": "Access Key",
"platformName": "Windows 10",
"project": "Untitled",
"w3c": true,
"plugin": "node_js-node_js"
}
}
const SafariMacosCapability = {
"browserName": "Safari",
"browserVersion": "16.0",
"LT:Options": {
"username": "Username",
"accessKey": "Access Key",
"platformName": "MacOS Ventura",
"project": "Untitled",
"w3c": true,
"plugin": "node_js-node_js"
}
}
const firefoxWindowsCapability = {
"browserName": "Firefox",
"browserVersion": "106.0",
"LT:Options": {
"username": "Username",
"accessKey": "Access Key",
"platformName": "Windows 11",
"project": "Untitled",
"w3c": true,
"plugin": "node_js-node_js"
}
}
const getElementById = async (driver, id, timeout = 8000) => {
const el = await driver.wait(until.elementLocated(By.id(id)), timeout);
return await driver.wait(until.elementIsVisible(el), timeout);
};
const getElementByXpath = async (driver, xpath, timeout = 10000) => {
const el = await driver.wait(until.elementLocated(By.xpath(xpath)), timeout);
return await driver.wait(until.elementIsVisible(el), timeout);
};
let sessionId = null;
describe('webdriver', () => {
let driver;
beforeAll(async () => {
driver = new webdriver.Builder()
.usingServer(
'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
)
.withCapabilities(chromeWindowsCapability)
.build();
await driver.getSession().then(function(session) {
sessionId = session.id_;
});
// eslint-disable-next-line no-undef
await driver.get("https://ecommerce-playground.lambdatest.io/index.php?route=account/register");
}, 50000);
afterAll(async () => {
await driver.quit();
}, 40000);
test('Test "Submit" without all fields being filled', async () => {
try {
const send = await getElementByXpath(driver, '//input[@value="Continue"]');
await send.click()
const FirstNameErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"First Name must be between 1 and 32 characters!")]');
FirstNameErrorMessage.getText().then(function(value) {
expect(value).toBe('First Name must be between 1 and 32 characters!');
});
const LastNameErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"Last Name must be between 1 and 32 characters!")]');
LastNameErrorMessage.getText().then(function(value) {
expect(value).toBe('Last Name must be between 1 and 32 characters!');
});
const EmailErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"E-Mail Address does not appear to be valid!")]');
EmailErrorMessage.getText().then(function(value) {
expect(value).toBe('E-Mail Address does not appear to be valid!');
});
const TelephoneErrorMessage = await getElementByXpath(driver, '//div[normalize-space()="Telephone must be between 3 and 32 characters!"]');
TelephoneErrorMessage.getText().then(function(value) {
expect(value).toBe('Telephone must be between 3 and 32 characters!');
});
const PasswordErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"Password must be between 4 and 20 characters!")]');
PasswordErrorMessage.getText().then(function(value) {
expect(value).toBe('Password must be between 4 and 20 characters!');
});
const PrivacyPolicyErrorMessage = await getElementByXpath(driver, '//div[@class="alert alert-danger alert-dismissible"]');
PrivacyPolicyErrorMessage.getText().then(function(value) {
expect(value).toBe('Warning: You must agree to the Privacy Policy!');
});
await updateJob(sessionId, 'passed');
} catch (err) {
await updateJob(sessionId, 'failed');
await webdriverErrorHandler(err, driver);
throw err;
}
}, 100000);
test('Test Invalid Email address submission', async () => {
try {
const firstName = await getElementById(driver, 'input-firstname');
await firstName.clear();
await firstName.sendKeys("James");
const lastName = await getElementById(driver, 'input-lastname');
await lastName.clear();
await lastName.sendKeys("Doe");
const Email = await getElementById(driver, 'input-email');
await Email.clear();
await Email.sendKeys("john@gmail");
const Telephone = await getElementById(driver, 'input-telephone');
await Telephone.clear();
await Telephone.sendKeys("0712345678");
const Password = await getElementById(driver, 'input-password');
await Password.clear();
await Password.sendKeys("12345");
const confirmPassword = await getElementById(driver, 'input-confirm');
await confirmPassword.clear();
await confirmPassword.sendKeys("12345");
const AgreePolicy = await getElementByXpath(driver, '//label[@for="input-agree"]');
await AgreePolicy.click()
const submit = await getElementByXpath(driver, '//input[@value="Continue"]');
await submit.click()
const EmailErrorMessage = await getElementByXpath(driver, '//div[@class="text-danger"]');
EmailErrorMessage.getText().then(function(value) {
expect(value).toBe('E-Mail Address does not appear to be valid!');
});
await updateJob(sessionId, 'passed');
} catch (err) {
await updateJob(sessionId, 'failed');
await webdriverErrorHandler(err, driver);
throw err;
}
}, 100000);
test('Test Account Created Success Message', async () => {
try {
const firstName = await getElementById(driver, 'input-firstname');
await firstName.clear();
await firstName.sendKeys("James");
const lastName = await getElementById(driver, 'input-lastname');
await lastName.clear();
await lastName.sendKeys("Doe");
const Email = await getElementById(driver, 'input-email');
await Email.clear();
await Email.sendKeys("jdoe@example.com");
const Telephone = await getElementById(driver, 'input-telephone');
await Telephone.clear();
await Telephone.sendKeys("0712345678");
const Password = await getElementById(driver, 'input-password');
await Password.clear();
await Password.sendKeys("12345");
const confirmPassword = await getElementById(driver, 'input-confirm');
await confirmPassword.clear();
await confirmPassword.sendKeys("12345");
const submit = await getElementByXpath(driver, '//input[@value="Continue"]');
await submit.click()
const SuccessMessage = await getElementByXpath(driver, '//h1[@class="page-title my-3"]');
SuccessMessage.getText().then(function(value) {
expect(value).toBe('Your Account Has Been Created!');
});
await updateJob(sessionId, 'passed');
} catch (err) {
await updateJob(sessionId, 'failed');
await webdriverErrorHandler(err, driver);
throw err;
}
}, 100000);
});
async function webdriverErrorHandler(err, driver) {
console.error('Unhandled exception! ' + err.message);
if (driver && sessionId) {
try {
await driver.quit();
} catch (_) {}
await updateJob(sessionId, 'failed');
}
}
function updateJob(sessionId, status) {
return new Promise((resolve, reject) => {
AutomationClient.updateSessionById(
sessionId,
{ status_ind: status },
err => {
if (err) return reject(err);
return resolve();
}
);
});
}
Since we are using selenium to run the tests, Selenium WebDriver, until, By and LambdaTest Rest Client are imported using require function as shown below:
const webdriver = require('selenium-webdriver');
const { until } = require('selenium-webdriver');
const { By } = require('selenium-webdriver');
const LambdaTestRestClient = require('@lambdatest/node-rest-client');
Environment variables LT_USERNAME and LT_ACCESS_KEY are assigned to variables username and accessKey.
const username = process.env.LT_USERNAME || 'your username';
const accessKey = process.env.LT_ACCESS_KEY || 'your accessKey';
A new automation client instance called AutomationClient with LambdaTest credentials is created.
const AutomationClient = LambdaTestRestClient.AutomationClient({
username,
accessKey
});
Test capabilities are configured by passing browser, browser version, and operating system information with LambdaTest Selenium Grid Capabilities through the capabilities object.
const chromeWindowsCapability = {
"browserName": "Chrome",
"browserVersion": "105.0",
"LT:Options": {
"username": "Username",
"accessKey": "Access Key",
"platformName": "Windows 10",
"project": "Untitled",
"w3c": true,
"plugin": "node_js-node_js"
}
}
const SafariMacosCapability = {
"browserName": "Safari",
"browserVersion": "16.0",
"LT:Options": {
"username": "Username",
"accessKey": "Access Key",
"platformName": "MacOS Ventura",
"project": "Untitled",
"w3c": true,
"plugin": "node_js-node_js"
}
}
const firefoxWindowsCapability = {
"browserName": "Firefox",
"browserVersion": "106.0",
"LT:Options": {
"username": "Username",
"accessKey": "Access Key",
"platformName": "Windows 11",
"project": "Untitled",
"w3c": true,
"plugin": "node_js-node_js"
}
}
An async function called getElementByXpath with parameters driver, xpath and timeout set to 2000 milliseconds is declared. Inside the async function, declare a variable called el, where the variable value instructs the Selenium driver to wait until the element with the targeted Xpath is located on the registration page of the e-commerce website, The elementLocated() method is used here.
An object returned by the getElementByXpath async function instructs the Selenium driver to wait until the element we are targeting by Xpath is present on the DOM of the registration page and visible. The elementIsVisible() method is used here.
const getElementByXpath = async (driver, xpath, timeout = 8000) => {
const el = await driver.wait(until.elementLocated(By.xpath(xpath)), timeout);
return await driver.wait(until.elementIsVisible(el), timeout);
};
A variable called sessionId is declared where the variable value is null as shown below.
let sessionId = null;
Create a describe block function called Registration Page Tests, where we will write all the tests for the e-commerce website registration page as shown below.
describe('Registration Page Tests', () => {
});
A variable called driver is declared, and a beforeAll() Jest method is created where it takes two parameters which are a function and timeout, as shown below.
describe('Registration Page Tests', () => {
let driver;
beforeAll(async () => {
}, 30000);
});
The beforeAll() method runs a function before any of the tests in a file run. If the function returns a promise or is a generator, Jest waits for that promise to resolve before running tests. You can provide a timeout in milliseconds to specify how long to wait before aborting.
A new instance of a driver is created using the new webDriver.Builder() constructor as shown below.
describe('Registration Page Tests', () => {
let driver;
beforeAll(async () => {
var driver = new webdriver.Builder()
}, 30000);
});
A usingServer() method that defines the remote URL for all builder instances is created where the URL should be fully qualified for a WebDriver server as shown below.
describe('Registration Page Tests', () => {
let driver;
beforeAll(async () => {
var driver = new webdriver.Builder()
.usingServer(
'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
)
}, 30000);
});
A withCapabilities() method that sets the desired capabilities when requesting a new session is created. In this case, the withCapabilities() method takes the desired capabilities stored in the chromeWindowsCapability, safariMacosCapability, and firefoxWindowsCapability variables declared earlier
.describe('Registration Page Tests', () => {
let driver;
beforeAll(async () => {
var driver = new webdriver.Builder()
.usingServer(
'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
)
.withCapabilities(chromeWindowsCapability)
}, 30000);
});
A build() method that generates an action that contains all the actions gathered and ready to be performed is created, as shown below.
describe('Registration Page Tests', () => {
let driver;
beforeAll(async () => {
var driver = new webdriver.Builder()
.usingServer(
'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
)
.withCapabilities(chromeWindowsCapability)
.build();
}, 30000);
});
Actions class in Selenium allows you to make a sequence of actions that you want to perform. Action in Selenium provides two methods, which are perform() and build(). These two methods of action are implemented by the actions class.
An instance method called getSession() that returns a promise for a session is created, as shown below.
describe('Registration Page Tests', () => {
let driver;
beforeAll(async () => {
var driver = new webdriver.Builder()
.usingServer(
'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
)
.withCapabilities(chromeWindowsCapability)
.build();
await driver.getSession().then(function(session) {
sessionId = session.id_;
});
}, 30000);
});
A get() method that opens the browser and navigates to the page URL that you specify inside the method’s parentheses is created. In this case, the URL we will use is the URL of this e-commerce website registration page that we want to test, as shown below.
describe('Registration Page Tests', () => {
let driver;
beforeAll(async () => {
var driver = new webdriver.Builder()
.usingServer(
'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
)
.withCapabilities(chromeWindowsCapability)
.build();
await driver.getSession().then(function(session) {
sessionId = session.id_;
});
await driver.get("https://ecommerce-playground.lambdatest.io/index.php?route=account/register");
}, 30000);
});
A afterAll() Jest method that takes two parameters which are function and timeout, is created as shown below.
afterAll(async () => {
}, 40000);
The fterAll()a method runs a function after all the tests in the file have been completed. If the function returns a promise or is a generator, Jest waits for that promise to resolve before continuing.
Inside the afterAll() method function, a quit() method is created. The quit() method quits the entire browser session with all its tabs and windows. WebDriver sessions will not close properly without the quit() method being called at the end, which could result in memory leaks.
afterAll(async () => {
await driver.quit();
}, 40000);
A test block function called test that will contain tests we want to run is created.
test('test', async () => {
}
A JavaScript try and catch statement is created, as shown below.
try {
await updateJob(sessionId, 'passed');
} catch (err) {
await updateJob(sessionId, 'failed');
await webdriverErrorHandler(err, driver);
throw err;
}
Runtime error is one of the errors that can be in the code where this type of error occurs during the execution of a program. Errors that occur during runtime are called exceptions.
PThe JavaScript try and catch statement handles the exceptions where the main code is inside the try block. If any errors occur while executing the try block, the errors go to the catch block.
Inside the try block, the path for web elements, input fields, and buttons on the registration page we want to be tested is defined.
To add the web elements path, navigate to the e-commerce registration page.
Then inspect the web page and get the ID locator of the input fields and buttons a user must fill and click when creating an account, as shown below.
In this case, we want to run three tests.
In Test Scenario 1 test, VariablesSubmit, FirstNameMessage, LastNameErrorMessage, EmailErrorMessage, TelephoneErrorMessage, PasswordErrorMessage, PrivacyPolicyErrorMessage are declared where they store the web elements path. Also, the tests are defined as shown below.
test('Test "Submit" without all fields being filled', async () => {
try {
const send = await getElementByXpath(driver, '//input[@value="Continue"]');
await send.click()
const FirstNameErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"First Name must be between 1 and 32 characters!")]');
FirstNameErrorMessage.getText().then(function(value) {
expect(value).toBe('First Name must be between 1 and 32 characters!');
});
const LastNameErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"Last Name must be between 1 and 32 characters!")]');
LastNameErrorMessage.getText().then(function(value) {
expect(value).toBe('Last Name must be between 1 and 32 characters!');
});
const EmailErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"E-Mail Address does not appear to be valid!")]');
EmailErrorMessage.getText().then(function(value) {
expect(value).toBe('E-Mail Address does not appear to be valid!');
});
const TelephoneErrorMessage = await getElementByXpath(driver, '//div[normalize-space()="Telephone must be between 3 and 32 characters!"]');
TelephoneErrorMessage.getText().then(function(value) {
expect(value).toBe('Telephone must be between 3 and 32 characters!');
});
const PasswordErrorMessage = await getElementByXpath(driver, '//div[contains(text(),"Password must be between 4 and 20 characters!")]');
PasswordErrorMessage.getText().then(function(value) {
expect(value).toBe('Password must be between 4 and 20 characters!');
});
const PrivacyPolicyErrorMessage = await getElementByXpath(driver, '//div[@class="alert alert-danger alert-dismissible"]');
PrivacyPolicyErrorMessage.getText().then(function(value) {
expect(value).toBe('Warning: You must agree to the Privacy Policy!');
});
await updateJob(sessionId, 'passed');
} catch (err) {
await updateJob(sessionId, 'failed');
await webdriverErrorHandler(err, driver);
throw err;
}
}, 100000);
In the code above, we have used Element Methods in Selenium such as send_keys() and click(). The send_keys() method in Selenium is used to send a text to any field, such as input fields, while the click() method is used to click on any element, such as a button or link on the web page being tested.
Other element methods in Selenium are such as
An async function called webdriverErrorHandler below the describe block with two parameters err, and the driver is created. Inside the async function, the error() method, which writes an error message to the console, is used.
In this case, we want the error message Unhandled exception written to the console. Then an if statement that checks if driver and sessionId are true is created. If they are true, the quit() method quits the entire browser session with all its tabs; else, a function called updateJob is called, as shown below.
async function webdriverErrorHandler(err, driver) {
console.error('Unhandled exception! ' + err.message);
if (driver && sessionId) {
try {
await driver.quit();
} catch (_) {}
await updateJob(sessionId, 'failed');
}
}
A function called updateJob with parameters sessionId and status is created. Inside the function, a JavaScript promise object is created. The promise object produces a value after an async operation completes successfully or an error if it does not complete successfully. The resolve function call indicates successful call completions, and the reject function call indicates errors.
function updateJob(sessionId, status) {
return new Promise((resolve, reject) => {
AutomationClient.updateSessionById(
sessionId,
{ status_ind: status },
err => {
if (err) return reject(err);
return resolve();
}
);
});
}
You can now execute the tests in the command line by running the command below.
npm test regform.test.js
Once the command completes running, you should see on the command line that the tests have passed, as shown below.
Visit your LambdaTest Dashboard, and on the right side of the screen, you should be able to see your recent tests, as shown below.
Click on one of the tests, and you will be redirected to the Automation Dashboard, as shown below.
The Automation Dashboard has all the information about the test where the information includes a video and a screenshot that shows you how the test went. You can also access the report on Analytics Dashboard, which shows all the details and metrics related to your tests.
Test Summary gives you a high-level overview of your test performance by showing how many tests passed and failed. Below is a screenshot with all details and metrics related to tests I run on cloud Selenium Grid and Jest.
In today’s competitive job market, it’s not enough to be a good software developer anymore. Developers who want to get ahead need to prove their expertise with a professional certification that shows they are at the top of their class.
The Selenium JavaScript 101 automation testing certification by LambdaTest is the ideal way to gain the skills and in-depth knowledge you need to excel in a JavaScript automation role. The certification provides learners with a comprehensive overview of best practices, core skills, and essential libraries common to most JavaScript automation frameworks.
As you have seen from the examples used in this Unit testing with Jest tutorial, Jest Framework can play a major role in ensuring that your code works as expected.
Apart from testing your code, you have also learned that you can combine Jest and Selenium to ensure your web app works as expected.
Delve into our top Unit Testing Interview Questions guide, designed to help you excel in unit testing interviews. It covers a wide range of topics, from syntax to advanced techniques, with detailed solutions.
Jest is a JavaScript testing framework developed by Facebook, and it's used for unit testing. The core of Jest is a mocking library, but Jest also includes an assertion library and an enzyme library for testing DOM manipulations.
Jest is a test runner that can be used to write, run, and debug tests. It was designed with the intention of being simple and easy to use. In order to accomplish this, Jest is not a full-featured testing framework like Mocha or Jasmine, but it is possible to use it for UI testing.
Try LambdaTest Now !!
Get 100 minutes of automation test minutes FREE!!
Did you find this page helpful?