Types of Testing
Testing is an integral part of the software development lifecycle, and understanding the different types of testing is essential for ensuring comprehensive test coverage. In this section, we will explore the main types of testing used in JavaScript web development: unit testing, integration testing, and end-to-end (E2E) testing.
Unit Testing
Unit testing is the process of testing individual units or components of a software application. A unit is the smallest testable part of an application, such as a function or a method. Unit tests are designed to validate that each unit of the software performs as expected.
They help identify bugs early in the development process and ensure that each unit behaves correctly.
Most of the time we write unit tests will be for functions in our code. We want to test that our functions behave the way we intended and we can do this by testing the input and output of the function to ensure it behaves as expected.
Note: With unit testing, we are checking that the function’s input and output behaves as expected.
For example, if we have a function that adds two numbers together, we would write a unit test to verify that the function correctly adds the numbers and returns the expected result.
Unit testing frameworks like Jest, Vitest, Mocha, and Jasmine are commonly used in JavaScript development.
Benefits of Unit Testing:
- Isolation: Each test targets a specific part of the code, making it easier to identify and fix bugs.
- Fast Feedback: Unit tests run quickly, providing immediate feedback on code changes.
- Documentation: Well-written unit tests can serve as documentation, explaining how different parts of the code are expected to behave.
Example of a Unit Test in JavaScript using Jest:
In the following example we have a sum
function which we want to test. We create a test file sum.test.mjs
to test the sum
function. The test file imports the sum
function from the sum.mjs
file and uses Jest’s test
function to define a test case. The expect
function is used to make assertions about the output of the sum
function.
// math.mjs
export function add(a, b) {
return a + b;
}
// math.test.js
import { expect, test } from 'vitest';
import { add } from './math.mjs';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3); // Test that the sum function correctly adds 1 and 2
});
Integration Testing
Integration testing focuses on verifying the interactions between different units or components of an application. The goal is to ensure that the combined units work together as expected. This type of testing is crucial for identifying issues that may arise when individual components are integrated.
Benefits of Integration Testing:
- Interaction Verification: Ensures that different parts of the system interact correctly.
- Detects Issues Early: Helps identify integration issues early in the development process.
- Comprehensive Testing: Provides a more comprehensive testing approach compared to unit tests alone.
Example of an Integration Test in JavaScript using Jest:
In the example below, we have two modules: userService.mjs
and orderService.mjs
. The orderService
module depends on the userService
module to fetch user data. We create an integration test file orderService.test.mjs
to test the createOrder
function in the orderService
module. The test file imports the createOrder
function from the orderService
module and uses Jest’s test
function to define a test case. The expect
function is used to make assertions about the output of the createOrder
function.
// userService.mjs
export function getUser(id) {
// Simulate fetching user data from a database
return { id, name: 'John Doe' };
}
// orderService.mjs
import { getUser } from './userService';
export function createOrder(userId, product) {
const user = getUser(userId);
return { user, product, status: 'Order Created' };
}
// orderService.test.mjs
import { createOrder } from './orderService';
test('creates an order for a given user and product', () => {
const order = createOrder(1, 'Laptop');
expect(order).toEqual({
user: { id: 1, name: 'John Doe' }, // Verify user data
product: 'Laptop',
status: 'Order Created'
});
});
End-to-End (E2E) Testing
End-to-End testing involves testing the complete flow of an application from start to finish. It simulates real user scenarios to ensure that the entire system works as expected. E2E tests validate the application’s functionality and performance by testing its interaction with external dependencies, such as databases and APIs.
Benefits of End-to-End Testing:
- User Perspective: Validates the application from the end-user’s perspective.
- Comprehensive Coverage: Tests the entire application flow, including interactions with external systems.
- High Confidence: Provides high confidence that the application works correctly in a real-world scenario.
Although this module does not cover E2E testing in detail, it is important to be aware of it as part of a comprehensive testing strategy.
Popular tools for E2E testing in JavaScript include Cypress and Selenium.
Example of an End-to-End Test using Cypress (conceptual):
In the example below, we use Cypress to write an E2E test that visits a web application and checks for a specific element on the page. The test file sample_spec.js
contains a test case that visits the application URL and checks if the page contains the text ‘Welcome’.
// cypress/integration/sample_spec.js
describe('My First Test', () => {
it('Visits the app and checks for a specific element', () => {
cy.visit('https://example.com'); // Visit the application
cy.contains('Welcome'); // Check if the page contains 'Welcome'
});
});
In the following example, we have a login flow test using Playwright, an end-to-end testing tool. The test navigates to the login page, fills in the username and password fields, clicks the login button, and expects the URL to change to the dashboard page.
// e2e/login.test.js
import { test, expect } from '@playwright/test';
test('login flow', async ({ page }) => {
await page.goto('https://example.com/login'); // Visit the application
await page.fill('#username', 'user'); // Complete the username input
await page.fill('#password', 'password'); // Complete the password input
await page.click('#login-button'); // Click the login button
await expect(page).toHaveURL('https://example.com/dashboard'); // Expect the URL to change
});
5. Other Types of Testing
Smoke Testing:
Smoke testing, also known as “sanity testing,” is a preliminary test to check the basic functionality of the application. It ensures that the critical features are working correctly.
Regression Testing:
Regression testing ensures that new code changes do not adversely affect the existing functionality of the application. It involves re-running previously passed tests to verify that the application still works as expected.
In summary, understanding the different types of testing helps ensure that your JavaScript applications are thoroughly tested and reliable. Unit testing, integration testing, and end-to-end testing each play a vital role in the overall testing strategy.