Jest is an open-source JavaScript testing framework developed and maintained by Meta (formerly Facebook). It is widely used for testing JavaScript applications, particularly those built with React. Jest simplifies writing and running tests, making it a popular choice among developers.
Overview
What are Parameterized Tests in Jest?
- Parameterized tests allow running the same test logic with multiple sets of inputs and expected outputs.
- They reduce repetitive code and make test cases concise and maintainable.
How Does Parameterized Testing in Jest Work?
Parameterized testing in Jest uses test.each or describe.each to run the same test logic across multiple input sets, dynamically generating and executing test cases for each combination.
When to Use Jest Parameterized Tests?
- Test multiple input/output combinations (e.g., math operations, string manipulations).
- Validate edge cases, boundary conditions, and different data types.
- Reduce code duplication for common test logic or scenarios.
- Perform data-driven testing for scalable, efficient test suites.
Types of Jest Parameterized Testing
- Basic Tests (test.each): Parameterize individual tests with input sets.
- Grouped Tests (describe.each): Organize related parameterized tests under one group.
- Asynchronous Tests: Run parameterized tests involving async operations.
- Table-Driven Tests: Use structured data tables for dynamic test generation.
- Snapshot Tests: Parameterize snapshot testing for different input scenarios.
- Nested Tests: Combine multiple levels of parameterized testing for complex cases
Why is Jest Used?
Jest ensures that JavaScript applications are reliable, maintainable, and bug-free. It simplifies the testing process for both front-end and back-end code. Here’s a detailed breakdown of why Jest is widely adopted:
1. Simplifies the Testing Process
- Out-of-the-box Setup: Jest comes pre-configured, so you can start testing without extra setup.
- Comprehensive API: It provides built-in tools for writing, mocking, and running tests, reducing the need for additional libraries.
2. Ensures Code Quality
- Unit Testing: Validates the smallest parts of your application, such as functions or methods, ensuring they behave as expected.
- Integration Testing: Checks if different modules or components of your app work together correctly. For broader integrations (for example, external APIs), additional libraries might be required.
- End-to-End Testing: Verifies that the entire application behaves as intended from a user’s perspective (can be used for basic end-to-end scenarios).
3. Detects UI Changes
- Snapshot Testing: Captures the output of components (for example, React components) and alerts developers if changes are introduced. This is especially useful for maintaining UI consistency.
4. Saves Development Time
- Fast Test Execution: Jest runs tests in parallel and re-runs only the tests affected by code changes, speeding up the development process.
- Mocking and Isolation: Allows developers to isolate parts of the application for focused testing without relying on external systems.
5. Encourages Better Practices
- Code Coverage Reports: Shows what percentage of your code is covered by tests, encouraging thorough testing.
- Debugging Support: Provides detailed error messages and stack traces to help locate and fix issues quickly.
Read More: Code Coverage Techniques and Tools
6. Versatility
- Works seamlessly with React, Vue, Node.js, and, with additional configuration, Angular.
- Supports TypeScript and modern JavaScript features, ensuring compatibility with a wide range of projects.
- Easily integrates into CI/CD pipelines for automated testing.
7. Maintains Application Stability
- Continuous testing ensures that changes in one part of the application don’t inadvertently break another part.
- By automating the testing process, Jest helps catch issues early, reducing the risk of bugs reaching production.
Key Features of Jest
Jest offers a range of powerful features that streamline testing and enhance developer productivity, including:
- Out-of-the-box Functionality: Jest comes preconfigured, so you can start testing without needing extensive setup or additional tools.
- Fast and Efficient: It runs tests in parallel and only re-tests the files that have changed, which speeds up the testing process.
- Built-in Assertion Library: Jest includes its own assertion library, eliminating the need to install additional assertion tools.
- Snapshot Testing: It allows developers to capture snapshots of components and compare them against future versions to detect unintended changes.
- Mocking Capabilities: Jest makes it easy to mock functions, modules, and timers, enabling isolation of components during testing.
- Code Coverage Reports: Generates comprehensive insights into test coverage, highlighting which parts of your codebase are thoroughly tested.
Read More: How to Debug Jest Tests
What are Parameterized Tests in Jest?
Parameterized tests in Jest allow you to run the same test with different sets of input values and expected outcomes. This helps in reducing repetitive code, making your tests more concise and maintainable.
How Parameterized Tests in Jest Work?
In Jest, parameterized tests are implemented using the .each() method, which allows you to supply a table of inputs and expected outputs to a single test. Jest then iterates through each set of parameters and runs the test for each one.
Syntax for Parameterized Tests
test.each(table)(name, fn);
- table: An array of input sets or a template literal table.
- name: A string describing the test case. You can interpolate input values using placeholders like ${}.
- fn: The function that defines the test logic, which receives parameters from the table.
Example 1: Simple Parameterized Test
test.each([ [1, 2, 3], [2, 3, 5], [3, 5, 8], ])('adds %i + %i to equal %i', (a, b, expected) => { expect(a + b).toBe(expected); })
Explanation:
- Each subarray ([1, 2, 3], etc.) represents a set of inputs (a, b) and the expected output.
- The test message (adds %i + %i to equal %i) is dynamically updated with the values for each test.
When should you use Jest Parameterized Tests?
Jest parameterized tests are ideal for situations where you want to test a single function or piece of code with multiple inputs and expected outputs. Here are some specific scenarios:
1. Testing Functions with Multiple Input/Output Combinations:
- Mathematical Operations: Testing functions like add, subtract, multiply, and divide with various input values.
- String Manipulation: Testing functions like trim, toUpperCase, and toLowerCase with different strings.
- Array Operations: Testing functions like map, filter, and reduce with various array inputs.
2. Testing Edge Cases and Boundary Conditions:
- Invalid Input: Testing how your function handles invalid or unexpected input values.
- Empty Input: Testing how your function behaves with empty input.
- Large Input: Testing how your function performs with large input datasets.
3. Testing Different Data Types:
- Numbers: Testing with integers, floats, and other numeric types.
- Strings: Testing with different character encodings and special characters.
- Objects and Arrays: Testing with various object structures and array lengths.
4. Testing Multiple Scenarios within a Single Test:
- Different User Roles: Testing how your application behaves for different user roles.
- Multiple Language Support: Testing how your application handles different languages.
- Different Browser Compatibility: Testing how your application works in different browsers.
5. Reducing Test Code Redundancy:
- Common Test Logic: If you have multiple tests with similar logic, parameterizing them can reduce code duplication.
- Data-Driven Testing: By parameterizing your tests, you can easily change the test data without modifying the test logic.
By using Jest parameterized tests, you can write more concise, efficient, and maintainable test suites. They help you achieve higher test coverage, improve code quality, and catch potential issues early in the development process.
Jest Parameterized Testing Types
Jest supports various types of parameterized testing, enabling you to test multiple scenarios with different input combinations efficiently. Here are some examples:
1. Basic Parameterized Tests with test.each
Jest’s test.each method allows you to write parameterized tests, enabling you to test the same functionality with multiple sets of inputs and expected outputs. This avoids repetitive test code and makes your tests concise and scalable.
Syntax of test.each
test.each(table)(name, fn);
- table: An array of test cases, where each test case is an array or object representing the input and expected output.
- name: A descriptive string for the test, which can include placeholders like %i, %s, or ${} for dynamic values.
- fn: The test function that uses the input parameters from the table.
Example of Basic Parameterized Test with Arrays
test.each([ [1, 2, 3], // Input: 1 + 2, Expected: 3 [2, 3, 5], // Input: 2 + 3, Expected: 5 [3, 5, 8], // Input: 3 + 5, Expected: 8 ])('adds %i + %i to equal %i', (a, b, expected) => { expect(a + b).toBe(expected); });
Output in Jest Console:
✓ adds 1 + 2 to equal 3 ✓ adds 2 + 3 to equal 5 ✓ adds 3 + 5 to equal 8
Read More: it.each Function in Jest
2. Parameterized Tests with describe.each
While test.each is used for running parameterized tests with different inputs and expected outputs, describe.each in Jest allows you to parameterize entire test suites. This is useful when you want to organize multiple tests or scenarios under the same group but with different inputs and expected results for each group.
Syntax of describe.each
describe.each(table)(name, fn);
- table: An array of test cases, where each test case is an array or object representing different inputs or configurations.
- name: A string for the test suite’s description, which can dynamically include the values from the table.
- fn: The function that defines the test cases and logic for each test suite iteration.
const sum = (a, b) => a + b; describe.each([ [1, 2, 3], [2, 3, 5], [4, 5, 9], [-1, -1, -2], [0, 5, 5], ])('sum(%i, %i)', (a, b, expected) => { test(`returns ${expected}`, () => { expect(sum(a, b)).toBe(expected); }); });
3. Asynchronous Parameterized Tests
In Jest, asynchronous parameterized tests allow you to run the same test logic with different input data sets, where the test logic may involve asynchronous operations. This can be particularly useful for ensuring your code works across a variety of scenarios while leveraging asynchronous features like promises or async/await.
Steps to Implement Asynchronous Parameterized Tests in Jest
- Use test.each or it.each: Jest provides test.each (or it.each) to create parameterized tests. You can supply an array of test cases, and Jest will run your test for each case.
- Write Async Test Logic: Use async/await inside the test function to handle asynchronous operations.
Example: Async Parameterized Test with test.each
const fetchData = async (input) => { // Simulates an async operation return new Promise((resolve) => { setTimeout(() => resolve(`Processed: ${input}`), 100); }); }; describe('Asynchronous Parameterized Tests', () => { const testCases = [ ['Input1', 'Processed: Input1'], ['Input2', 'Processed: Input2'], ['Input3', 'Processed: Input3'], ]; test.each(testCases)( 'fetchData(%s) resolves to %s', async (input, expectedOutput) => { const result = await fetchData(input); expect(result).toBe(expectedOutput); } ); });
4. Parameterized Table-Driven Tests
Parameterized (or table-driven) tests in Jest allow you to define a table of input data and expected outcomes for a test, enabling you to run the same logic across multiple scenarios. Jest provides methods like test.each or it.each to implement this pattern effectively.
Basics of Table-Driven Tests in Jest
1. test.each or it.each:
- Used for defining table-driven tests.
- Accepts an array of data (a “table”).
- Each row in the array is passed as individual arguments to the test function.
2. Table Formats:
- Inline arrays: Define the table directly as an array of arrays.
- Tagged templates: Use template literals for better readability.
Example:
describe('Table-Driven Tests - Inline Table', () => { test.each([ [1, 2, 3], // [a, b, expected] [5, 5, 10], [2, 3, 5], ])('adds %i + %i to equal %i', (a, b, expected) => { expect(a + b).toBe(expected); }); });
- Each array in the table represents a single test case.
- The variables a, b, and expected correspond to the values in each row.
Read More: How to Configure Jest
5. Parameterized Snapshot Tests
Parameterized snapshot tests in Jest allow you to dynamically test multiple input-output scenarios while leveraging Jest’s built-in snapshot testing functionality. This is particularly useful when you want to verify that a component or function produces consistent outputs across different sets of inputs.
Setting Up Parameterized Snapshot Tests
You can use test.each or it.each to define a table of input-output cases and generate snapshots for each one.
Example:
const formatData = (data) => { return { ...data, formatted: true }; }; describe('Parameterized Snapshot Tests - Function Output', () => { test.each([ { input: { name: 'Alice' }, description: 'with name Alice' }, { input: { name: 'Bob' }, description: 'with name Bob' }, { input: { age: 30 }, description: 'with age 30' }, ])('formats data $description', ({ input }) => { const result = formatData(input); expect(result).toMatchSnapshot(); }); });
Here,
- The test.each runs the snapshot test for each input in the array.
- Snapshots are saved separately for each test case.
- Descriptions in the table make snapshots easier to identify.
6. Nested Parameterized Tests
Nested parameterized tests in Jest allow you to test combinations of inputs by nesting test.each (or it.each) within a describe.each or another test.each. This is particularly useful when you want to test a set of dependent parameters or multiple layers of variability systematically.
Structure of Nested Parameterized Tests
- Outer loop: Defines a higher-level of level of variation (for example, different test categories or contexts).
- Inner loop: Defines specific cases or scenarios for each category.
Example:
const add = (a, b) => a + b; describe.each([ ['positive numbers', [1, 2], [3, 4], [5, 6]], ['negative numbers', [-1, -2], [-3, -4], [-5, -6]], ['mixed numbers', [1, -2], [-3, 4], [5, -6]], ])('Addition with %s', (description, ...cases) => { test.each(cases)('add(%i, %i) equals %i', (a, b) => { expect(add(a, b)).toBe(a + b); }); });
- The outer loop (describe.each) iterates over the categories of numbers.
- The inner loop (test.each) iterates over individual test cases for each category.
Read More: Understanding Jest BeforeEach function
Different Formats for Test Data Inputs
Jest supports various formats for test data inputs, making it flexible and easy to organize tests for different scenarios.
1. Inline Data
- Simple Arrays: Use inline arrays for straightforward test cases.
test('adds 1 + 2 to equal 3', () => { expect(1 + 2).toBe(3); });
- Object Literals: Test specific object properties inline.
test('object assignment', () => { const data = { one: 1, two: 2 }; expect(data.one).toBe(1); expect(data.two).toBe(2); });
2. Data-Driven Testing with it.each
- Array of Arrays: Organize test cases in a table format for multiple scenarios.
const testCases = [ [1, 2, 3], [4, 5, 9], [6, 7, 13], ]; it.each(testCases)('adds %i + %i to equal %i', (a, b, expected) => { expect(a + b).toBe(expected); });
- Array of Objects: Use destructuring to improve readability.
const testCases = [ { a: 1, b: 2, expected: 3 }, { a: 4, b: 5, expected: 9 }, { a: 6, b: 7, expected: 13 }, ]; it.each(testCases)('adds $a + $b to equal $expected', ({ a, b, expected }) => { expect(a + b).toBe(expected); });
3. External Data Files
- JSON: Import JSON files directly for test data.
const testData = require('./testData.json'); it.each(testData)('tests with data from JSON', (test) => { // ... });
- CSV: You can use libraries like csv-parser to parse CSV files and use the parsed data in your tests.
Read More: How to Test React App using Jest
Primitive Types in Jest
Jest allows you to test primitive types (like string, number, boolean, null, undefined, symbol, and bigint) in various ways. These are some scenarios and examples of how you can effectively test primitive values in Jest:
1. Testing Numbers
Basic Equality: Use toBe for exact comparisons or toEqual (though toBe is sufficient for primitives).
test('numbers match', () => { expect(2 + 2).toBe(4); expect(3.14).toEqual(3.14); });
2. Testing Strings
String Comparisons: Use toBe for exact matches or toMatch for patterns using regular expressions.
test('string equality', () => { expect('hello').toBe('hello'); }); test('string matches regex', () => { expect('hello world').toMatch(/world/); });
3. Testing Booleans
Use toBe for strict boolean checks.
test('boolean values', () => { expect(true).toBe(true); expect(false).toBe(false); });
Tuple Types
Testing tuple types in Jest involves ensuring that arrays with fixed types and lengths (tuples) conform to the expected structure and values. Tuples are common in TypeScript for representing small, structured collections of values.
Testing Tuple Values
For fixed-length arrays with specific types, you can directly assert the structure and values using Jest matchers.
Example:
type Point = [number, number]; test('tuple matches expected structure', () => { const point: Point = [1, 2]; expect(point).toEqual([1, 2]); // Matches value and order });
Plain Object Types
Testing plain object types in Jest typically involves validating their structure, properties, and values. Jest provides several matchers specifically designed for testing plain JavaScript objects, making it easy to handle objects with nested properties, optional keys, and specific types.
Example:
Use toEqual to check if two objects have the same structure and values.
test('plain object equality', () => { const obj = { name: 'Alice', age: 30 }; expect(obj).toEqual({ name: 'Alice', age: 30 }); });
Why should you test Jest tests on Real Devices?
While Jest is primarily a testing framework for unit and integration tests, directly testing Jest tests on real devices isn’t typically necessary. Jest’s core functionality focuses on testing JavaScript code in a controlled environment, often using mock functions and virtual DOM implementations.
However, there are specific scenarios where testing on real devices can provide valuable insights:
- Real-World Behavior: Real devices can expose issues related to browser-specific quirks, device performance, and network conditions that might not be apparent in simulated environments.
- User Experience: Testing on real devices can help identify potential UI/UX problems that might not be visible in emulators or simulators.
- Integration with Other Technologies: If your application interacts with other technologies like native APIs, third-party libraries, or hardware sensors, testing on real devices can ensure seamless integration.
- Pixel-Perfect Comparisons: Real devices can help identify visual discrepancies that might be missed in simulated environments, especially when dealing with complex layouts, animations, or device-specific rendering differences.
- Real-World Performance Metrics: Testing on real devices can provide insights into actual performance metrics like load times, frame rates, and resource usage, which can vary significantly across devices.
Setting up Jest for Parameterized Tests
Setting up Jest for parameterized tests involves using the test.each or it.each methods, which allow you to run the same test logic across multiple sets of data inputs. Here’s a step-by-step guide to setting up and using parameterized tests effectively in Jest:
1. Install Jest
If Jest is not already installed in your project, set it up with:
npm install --save-dev jest
Ensure your package.json includes a script to run Jest:
"scripts": { "test": "jest" }
Run the tests using:
npm test
2. Understanding Parameterized Tests in Jest
Parameterized tests in Jest are achieved using:
- test.each: Runs a test multiple times with different sets of data.
- it.each: Alias for test.each.
These functions allow you to define test cases dynamically, reducing code duplication.
3. Writing Parameterized Tests
The it.each function takes an array of test cases and a callback function. Each test case is passed as arguments to the callback function.
test.each([ [1, 1, 2], [2, 2, 4], [5, 2, 7], ])('adds %i + %i to equal %i', (a, b, expected) => { expect(a + b).toBe(expected); });
Breakdown:
- it.each: This function is used to parameterize the test cases.
- [[1, 1, 2], [2, 2, 4], [5, 2, 7]]: This is an array of test cases, each containing an array of input values and the expected output.
- ‘adds %i + %i to equal %i’: This is the test title, where %i placeholders will be replaced with the actual values from each test case.
- (a, b, expected) => { … }: This is the callback function that will be executed for each test case. The a, b, and expected parameters will be assigned the values from the corresponding test case.
How to run Jest Tests with BrowserStack?
Follow these quick steps to run your Jest tests on BrowserStack’s Selenium Grid with 3,500+ real devices and browsers.
Prerequisites:
- Get your BrowserStack Username and Access Key from your account profile. If you don’t have an account, sign up for a Free Trial.
- Ensure Node.js v12+ is installed on your machine.
Steps to Run Jest Tests:
1. Download the Sample Project
- Option 1: Download the project as a ZIP file.
- Option 2: Clone the sample repository from GitHub.
2. Configure BrowserStack Credentials
- Open the project and find the browserstack.yml file.
- Add your BrowserStack Username and Access Key to the file.
3. Choose Browsers and Devices
- In the same browserstack.yml file, select the browsers and devices you want to test on from BrowserStack’s 3,500+ real devices and browsers.
4. Run the Sample Test
- Run the test using the command from the project’s root folder.
5. View Test Results
- After running the test, check your results on the BrowserStack Automate Dashboard.
Next Steps:
Once your first test runs successfully, you can integrate your test suite with BrowserStack.
For more details, refer to this documentation for running Jest tests in BrowserStack.
Conclusion
Jest is a powerful and modern testing library with advanced features like it.each(), which simplifies data-driven testing. It supports various types of testing, including end-to-end testing, which often requires validating across multiple browsers, platforms, and devices. Running Jest tests on a cloud platform like BrowserStack ensures scalability and seamless cross-browser testing.
BrowserStack Automate integrates with popular automation frameworks, enabling you to run comprehensive tests effortlessly. You can also integrate your test suite into a CI/CD pipeline for automated testing during deployments, ensuring a smoother development process.