JavaScript Testing with Jest — Demystifying the Quest for Bulletproof Code
Testing JavaScript applications used to be a cumbersome and error-prone process. However, with the advent of Jest, that narrative has changed dramatically. Jest, known for its simplicity and speed, has revolutionized the way JavaScript developers approach testing. It’s no longer a chore. It’s an integral part of the development workflow. Jest is an open-source testing framework maintained by Facebook that provides developers with a seamless and enjoyable testing experience. It combines simplicity with powerful testing capabilities, making it an ideal choice for projects of all sizes.
In this article, we will delve into Jest’s features and understand why it’s a game-changer for JavaScript developers.
Installation and Setup
Open a Folder in VS Code (or any other editor) — create a project called SampleProjOne
Fire up the Terminal and hit the following commands:
- npm init -y
- npm install --save-dev jest
In package.json under scripts -> test, specify “jest”
Writing the first testcase
Here let’s say I have a JavaScript file for finding the nth Fibonacci Sequence number. And saved it as fibonacci.js
const getFibonacciOf = (n) => {
let a = 0, b = 1, c;
for (let i = 2; i <= n; i++) {
c = a+b;
a = b;
b = c;
} return c;
};module.exports = getFibonacciOf;
Now let’s create a folder for all test files, called Tests.
Under Tests folder, create a file with the same filename which we want to write test cases for, but with a .test.js file extension.
Thus, our filename would be fibonacci.test.js, which would be checking if our Fibonacci Series code is correct and flag any potential chinks in our code armor.
const fibonacci = require('../fibonacci');
test('When n=3', () => {
const res = fibonacci(3);
expect(res).toEqual(2);
});
So, this is our test/jest file. Here, test() contains our sample test case. We can provide a custom name for the testcase. Here I’ve given it as ‘When n=3’ indicating that this testcase is for testing our Fibonacci code for finding the 3rd Fibonacci number in the series.
Here at line 6: expect(res).toEqual(2);
expect(res): This part of the statement, is setting up an expectation or assertion. It means we expect a certain value or behavior from the code being tested. In this case, we are expecting the value stored in the res variable, which is 2
.toEqual(2): It checks whether the value on the left-hand side (res in this case) is equal to the value on the right-hand side (2 in this case). If the values are equal, the test case will pass; else, the test case will fail.
Running our testcase
To run our test case, we would be running our test file.
The command to run test file: npm run test (testFile)
Once the all the testcases in our test file has been run, it gives a summary of the different test that has been run, indicating the number of cases that has passed, failed and time it took.
It also highlights the total number of Test-Suites and the number of which passed and failed.
Let’s say we write a test case for getting the 0th Fibonacci number
const fibonacci = require('../fibonacci');
test('When n=3', () => {
const res = fibonacci(3);
expect(res).toEqual(2);
});test('When n=0', () => {
const res = fibonacci(0);
expect(res).toEqual(0);
});
The test case has failed when n=0
Thus, when we have a look at our code, we see that when n=0, we return c, but c has not yet been defined because, it only does when the for loop starts. We can also see that we haven’t handled the case when n=1.
Now after realizing what edge cases (when n=0 & n=1) we missed in our code from those testcases, fibonacci.js looks like this:
const getFibonacciOf = (n) => {
let a = 0, b = 1, c;
for (let i = 2; i <= n; i++) {
c = a+b;
a = b;
b = c;
} if (n==0)
return a;
else if (n==1)
return b;
else
return c;
};module.exports = getFibonacciOf;
After adding testcases for n=0 & n=1, our fibonacci.test.js looks like
const fibonacci = require('../fibonacci');
test('When n=3', () => {
const res = fibonacci(3);
expect(res).toEqual(2);
});test('When n=0', () => {
const res = fibonacci(0);
expect(res).toEqual(0);
});test('When n=1', () => {
const res = fibonacci(1);
expect(res).toEqual(1);
});
Now when we run the test cases again
Writing Test-Suites
In Jest, a test suite is a way to group related test cases together. It provides a structured organization for your tests, making it easier to manage and maintain your test code, especially in larger projects. Test suites help you categorize tests based on different parts of your codebase or specific functionality.
Here’s how you can create a test suite using Jest:
const fibonacci = require('../fibonacci');
describe('Testing Fibonacci Series', () => { test('When n=3', () => {
const res = fibonacci(3);
expect(res).toEqual(2);
});
test('When n=7', () => {
const res = fibonacci(7);
expect(res).toEqual(13);
});
});
You can have multiple test suites in Jest. In fact, organizing our tests into multiple test suites is a common practice, that’s extremely handy when dealing with larger codebases or complex projects. It helps keep our tests organized, makes it easier to manage and run specific sets of tests, and provides a clear structure for our test suite hierarchy.
To create multiple test suites in Jest, we can use the describe function multiple times to define different test suite blocks.
Let’s create a new file which has basic math operations like Addition, Subtraction and Multiplication.
function addNumbers(a, b) {
return a + b;
}
function subtractNumbers(a, b) {
return a - b;
}function multiplyNumbers(a, b) {
return a * b;
}module.exports = {
addNumbers,
subtractNumbers,
multiplyNumbers
};
And a corresponding Test file for this would be like:
Here we have segregated the different testcases for each of the functions into separate Test Suites.
const { addNumbers, subtractNumbers, multiplyNumbers } = require('../basicMath');
describe("Addition", () => { test("1 & 2", () => {
let res = addNumbers(1,2);
expect(res).toEqual(3);
});
test("50 & 67", () => {
let res = addNumbers(50,67);
expect(res).toEqual(117);
});});describe("Subtraction", () => { test("1 & 2", () => {
let res = subtractNumbers(1,2);
expect(res).toEqual(-1);
});
test("150 & 60", () => {
let res = subtractNumbers(150,60);
expect(res).toEqual(90);
});
});describe("Multiplication", () => { test("1 & 2", () => {
let res = multiplyNumbers(1,2);
expect(res).toEqual(2);
});
test("3 & 12", () => {
let res = multiplyNumbers(3,12);
expect(res).toEqual(36);
});
});
Different types of Matches
Matchers are an integral part of writing effective tests. They help us define the expected outcome of our code and ensure that it behaves as intended. Jest’s matchers make it easier to write clear and concise test cases, which ultimately leads to better code quality and maintainability. There are various types of matchers giving developers the flexibility and ease of use.
- toEqual(expected)
test('checking toEqual()', () => {
expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 });
});
One of the most commonly used matchers, just like the name sounds, it checks if the returned value is same as the expected value. Apart from that it is also used for deep equality comparisons of objects and arrays. It recursively checks if the actual and expected values have the same properties and values. This is particularly useful when comparing complex data structures.
2. toBe(expected)
test('adding 1 + 2 equals 3', () => {
expect(1 + 2).toBe(3);
});
The toBe() matcher is used for comparing primitive values like numbers, strings, or Booleans. It checks if the actual value is strictly equal (===) to the expected value. This matcher ensures that not only the values are the same but also that they have the same data type.
3. toContain(expected)
test('Checking if an array contains a particular value', () => {
const fruits = ['BMW', 'Lamborghini', 'Alfa Romeo', 'Lexus', 'Dacia'];
expect(fruits).toContain('Alfa Romeo');
});
The toContain() matcher is used to check if an array or string contains the expected value. It’s handy for verifying the presence of specific elements in collections.
4. toMatch(pattern)
test('Matching a string using a regex pattern', () => {
expect('Porsches are reliable').toMatch(/reliable/);
});
The toMatch() matcher is used to match a string against a regular expression pattern. It’s useful for verifying if a string conforms to a specific format.
5. toBeTruthy() & toBeFalsy()
test('Checking if a value is truthy', () => {
expect(1).toBeTruthy();
});
test('Checking if a value is falsy', () => {
expect(null).toBeFalsy();
});
The toBeTruthy() and toBeFalsy() matchers are used to check if a value is truthy or falsy, respectively. They are helpful for verifying boolean values or conditions.
6. toThrow(error)
function throwError() {
throw new Error('This is an error');
}
test('Checking if a function throws an error', () => {
expect(throwError).toThrow('This is an error');
});
The toThrow() matcher is used to test if a function throws an error. You can optionally specify the expected error message or error class.
Conclusion
Thus, Jest proves to be an indispensable tool for JavaScript developers, offering a robust testing framework with a plethora of matchers that enhance the efficiency and reliability of code testing. When dealing with huge Backend applications that handle numerous APIs, Jest helps us developers by easily highlighting our Achilles’ arm, foot and face. Its simplicity, speed, and extensive features make it a powerful ally in ensuring code quality, and to write tests with ease and confidence.
About the Author
CJ (Chiran Jeevi) is a Full Stack Developer with about a year of experience in the Tech field. He’s currently working and exploring VueJS, NodeJS and AWS DynamoDB, and is always hunting to come up with new tech solutions. Apart from having a profound fascination with technology, programming and Cybersecurity, he’s also an avid car and bike enthusiast often engaging in new activities that keep venturing him out of his comfort zone.
About CodeStax.Ai
At CodeStax.Ai, we stand at the nexus of innovation and enterprise solutions, offering technology partnerships that empower businesses to drive efficiency, innovation, and growth, harnessing the transformative power of no-code platforms and advanced AI integrations.
But the real magic? It’s our tech tribe behind the scenes. If you’ve got a knack for innovation and a passion for redefining the norm, we’ve got the perfect tech playground for you. CodeStax.Ai offers more than a job — it’s a journey into the very heart of what’s next. Join us, and be part of the revolution that’s redefining the enterprise tech landscape.