Unit Testing: Beginners guide
Introduction
Think about it, how is it that builders are able to erect a tall, straight wall? By checking each block laid with a plumb line to make sure they are on a straight line. That is what every developer does to each function, method and code to make sure they give the expected output.
JavaScript unit testing is a JavaScript testing framework built by Facebook primarily to test React-based apps. It can actually be used to test any JavaScript codebase.
In this tutorial, you will be learning
• What unit testing is all about
• Steps to unit testing
• Properties of a good unit testing
• Advantages and Disadvantages of unit testing
• Installation of jest
• How to write a Simple unit testing
• Matchers
• How to test for numbers, String, object, array and truthy
What is unit testing
Unit testing is one of the early precautions taken during the software development process. It is a process in which the smallest, testable part of an application is scrutinized to see if it works as intended. It involves the isolation of code such as function or method into small independent units and testing them individually. The idea is to pick these functions and methods and isolate them by replacing the external dependencies like the network connection and database with dummy objects.
Steps to unit testing
As a developer, you might not be able to test every line of code as this will waste a lot of your time, it is important to focus on codes that are critical to the performance of your application.
Unit testing is a component of test-driven development (TDD). TDD is a methodology that involves three critical activities that are interwoven. They include unit testing, coding and design (in the form of refactoring of code). TDD is all about Red/Green testing. This means that we would write the test, fail the test, and then finally write the code to pass the test.
Properties of a good unit test
- Small and focused: testing a single unit of code makes it easier to debug and locate the source of the problem
- Write the test before the code. This makes it easier for developers to think about the functionality and clarity of code. It makes it easy to write testable code.
- Use of descriptive test names. This is important as it helps developers to understand what the test code is meant to do.
- Use assertion (expressions containing testable codes) to ensure that the code output matches the desired behaviour.
- Test edge cases such as boundary condition, null, empty input and other less common scenarios that could still occur.
- Run tests regularly
- Refactor the codes and make sure they test the intended functionality
Advantages of unit testing
a. It helps in the early detection of bugs
b. It makes it easier for a developer to change the code base
c. It makes fixing errors cheaper
d. Improves code quality
e. Faster development cycle as it reduces the time for debugging
Disadvantages of unit testing
- It won’t uncover every bug
- It won’t catch errors in integration with other data sets
- Multiple lines of test codes need to be written to test one line of code
- It has a steep learning curve
Installation of Jest
We will look at how to write a simple unit test in JavaScript using Jest.
To do this you need to make sure you already have node and vscode installed in your computer.
Open your Vscode terminal and type the following command
a. npm init –y this helps you to install the node module dependencies and the package.json file
b. npm i –D jest this installs the jest
c. In your package.json file add the following section
{
“script”: {
“test”: “jest”
}
}
d. Your package.json file should look like this
{
"name": "jest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^29.7.0"
},
}
How to write a simple Jest Testing
- Create a file named function.js. This is where you write the function you want to test. In this case, the name of the function is “add”. You will notice that it is isolated and packaged inside an object called functions. This function object is then exported to the file where it is going to be tested
const functions = {
add: (num1, num2) => num1 + num2,
}
module.exports = functions
- Import the function into the test function, function.test.js
const functions = require('./function')
test('Adds 2 + 2 to equal 4', () => {
expect(functions.add(2,2)).toBe(4);
});
In line 1, the code is imported from the function file.
In the next line of code, the test method is used to test the function imported from the function file.
The test method takes two parameters, the first parameter is a string and gives us a description of what is being tested. The second parameter is a function that uses the expect method to test the add function. In this case, the add function accepts the required parameters, 2 and 2. This is then compared to the output which is 4 using the toBe method.
- At the terminal, type "npm test" to test the code if it passed or failed.
We might want to just include the function in the test function rather than importing it from an outside file. For Example
test('Should be under 1600', () => {
const load1 = 800;
const load2 = 700;
expect(load1 + load2).toBeLessThan(1600)
})
Matchers
In jest, matchers are used to test values in different ways. There are different types of matchers depending on the kind of value you want to test.
Equality Matcher
In the previous examples when the code expect(function add(2 + 2) returns an expectation, you will need to add a matcher .toBe(4) to them in order to ascertain if the outcome matches. toBe matcher test for an exact equality of values using the Object.is
test('Adds 2 + 2 to equal 4', () => {
expect(functions.add(2,2)).toBe(4);
});
We can also test the opposite of a matcher using the not. Take a look at the following test code.
test('addition of positive numbers not equal to zero', () => {
for(let i = 1; i < 10; i++){
for(let j = 1; j < 10; j++){
expect(i + j).not.toBe(0)
}
}
})
In this test code, we are testing a set of two numbers sum together using nested for loop. We want to find out the chances that the sum of any two numbers is not equal to zero
Floating Number Matcher
When testing for floating numbers, we don’t use .toBe matcher as it will fail because of rounding error. We rather use .toBeCloseTo matcher. For example
test('adding floating point number', () => {
const value = 0.53 + 0.23;
expect(value).toBeCloseTo(0.76)
})
Array and Object Matcher
While the .toBe matcher is for primitive number values, we can decide to test for arrays and objects using the .toEqual matcher. This compares object and array recursively (repetitively) by checking every field in the object and array.
test('comparing object', () => {
const detail = {
name: "Franklin",
age: 14
}
detail["gender"] = "male"
expect(detail).toEqual({name: "Franklin", age: 14, gender: "male"})
})
We can check if an iterable such as an array contains an item using the toContain matcher. toContain matcher actually uses the (===) strict equality to check if an array contains an item. It can also be used to check if a string is a substring of another string.
test("check if 'Mazda' is in the list of cars", () => {
const carList = [
"Toyota",
"Volvo",
"Mazda",
"Honda",
"Kia"
]
expect(carList).toContain("Mazda")
})
Number Matcher
We can also test for numbers using the equivalent matcher. For example
test('testing for numbers using equivalent matcher', () => {
const value = 2 + 2
expect(value).toBe(4)
expect(value).toEqual(4)
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3.5)
expect(value).toBeLessThan(5)
expect(value).toBeLessThanOrEqual(4.5)
})
From the above test code, we can see that it is possible to use more than one expect code to test for numbers in a test code.
String Matcher
We can test for a string using a .toMatch matcher and a regular expression
test("there is no V in psychology", () => {
expect("psychology").not.toMatch(/V/)
})
test("check for 'urc' in Church", () => {
expect('Church').toMatch(/urc/)
})
Truthy Matcher
We can test for truthy and falsy values using:
toBeNull which matches only Null
toBeUndefined which matches only undefined
toBeDefined which is the opposite of toBeUndefined
toBeTruthy which matches anything an if statement treats as true
toBeFalsy which matches anything an if statement treats as false
Information at the Terminal: When the test passed
When we run the test and it passes, we are expecting to see the following information in our terminal. From the information provided at the terminal, we can see that there are 12 tests carried out and they all passed. At the end of each of them, you can also see the amount of time taken for the test to be executed. As a programmer, it is important we try to write codes that get executed in the shortest possible time as it will mean that our application will run fast.
From the image above you can see that the statements underlined in yellow are the string in the test function which describes what we are testing, while the time taken to execute is circled in red. The part circled with a blue marker gives us a summary of the whole test carried out. The total number of tests was 12 and the total time spent on all the tests was 0.838 sec. If you look closely you will notice that one of the tests has the highest time of execution, 17 ms. This is because it is a test for a nested for loop and had the highest time of execution.
When the test failed
If we take a look at the summary of the test, we will see that there are 12 tests carried out in total but only 11 passed the test. The one that did not pass is also shown in red mark. The description of the failed test is also shown in red. It goes further to indicate what is expected as against what it received from the input. This information helps us to input the right information so as to pass the test.
Conclusion
In this tutorial, we have been able to examine what unit testing is all about, how to install jest, how to write, the different matchers and finally a brief interpretation of the information at the terminal.
Top comments (0)