How to Use Jest each to Get More Code Coverage with Less Test Code

2 months ago 26

Jest has been the most popular testing library for JavaScript for the last 5 years. You can use Jest each to write fewer tests and get more test code coverage. In this post, you will learn how to use the Jest’s each method to write efficient unit tests that result in better code coverage. Let’s get started! Table of Contents # Jest each Example of E-Commerce Order Status Code Example of Customer Order Status Code example without Jest each Code Example with Jest each Conclusion Jest each # Jest each is a feature that lets you write data-driven tests. It can be used to test the same code with different inputs and outputs. You can use Jest each to write data-driven tests where you can test the same code with different inputs and outputs without repeating the test code. It will automatically generate test titles for each of the test cases you feed to each which makes it easier to understand which test failed. It is a great way to write exhaustive tests for your code. If you have used PHPUnit to test PHP code, it is very similar to PHPUnit data provider. The idea is that, in place of looping through an array of data in your test function and calling the assert function. You can put that data in a table-like structure and pass it to each. The each function will then loop through each row of data and execute the test for that row. It will also make your test output more readable with meaningful test case names generated from the data you supplied. You can use each in two ways: Table format - where data is an array of arrays, the first row is the table header and the other rows are the data Tagged template literal - you can use tagged template literals to write tests in a more readable way. This format can also give you the benefit of formatted values in the test title. We will look at the code examples of both formats below: Table format describe("Math.pow", () => { test.each([ [2, 1, 2], [2, 2, 4], [2, 3, 8], ])( "Math.pow(%i, %i) should return %i", (base, exponent, expected) => { expect(Math.pow(base, exponent)).toBe(expected); } );}); Below is an example of the tagged templated literal format for Jest each with above math power example: Tagged template literals describe("Math.pow", () => { test.each`base | exponent | expected${2} | ${1} | ${2}${2} | ${2} | ${4}${2} | ${3} | ${8}`( "Math.pow($base, $exponent) should return $expected", ({ base, exponent, expected }) => { expect(Math.pow(base, exponent)).toBe(expected); } );}); Example of E-Commerce Order Status # As an example of how Jest each can make your code more readable and provide more code coverage, let’s take the case of an e-commerce website. When a customer orders an item from an e-commerce website, the order goes through a lifecycle of order statuses. There are generally two types of order statuses on an e-commerce website. One is the internal order status visible to the employees of the company. This order status defines the stage the order is at. This order status helps the company track the order internally. Some of the internal order statuses can be: Created - a new order is placed Sent to warehouse - the order has been sent to the warehouse system. Picked at warehouse - the order has been picked and put into a box at the warehouse. Sent to courier - The order is created on the courier’s system and the box is ready for pickup. Picked up by courier - The order and it’s item are in a box and are with the courier. Out for Delivery - The parcel is being sent to the custmer by the courier. Delivered - the items have been successfully delivered to the customer There is another order status that is shown to the customer. This order status is more user-friendly and can be something like the following: Processing - equivalent to the above internal statues from Created to sent to courier. Order shipped - equivalent to the above internal statues of Picked up by courier and out for delivery. Delivered - equivalent to delivered in the above internal statuses. For this example, let's write some code and a test for the mapping of internal order status to customer order status using each in Jest, in the next section. Code Example of Customer Order Status # Below is a simple Javascript function that maps the internal order status to the customer order status. export function getPublicOrderStatus(interrnalStatus) { const processing = 'processing' const statusMap = { 'created': processing, 'sent_to_warehouse': processing, 'picked_at_warehouse': processing, 'sent_to_courier': processing, 'picked_up_by_courier': 'shipped', 'out_for_delivery': 'shipped', 'delivered': 'delivered', }; if (statusMap[interrnalStatus]) { return statusMap[interrnalStatus]; } return processing;} The above code snippet defines a const statusMa that has keys for all the different internal order statuses. Then we define a getCustomerOrderStatus function that takes the internal order status as a parameter and returns the user-friendly order status that will be visible to the customer. It uses a Map with an Object that has the internal ordre status as the key and the customer friendly order status as the value. Code example without Jest each # Below is a code example to test that the mapping from internal to customer order status is working as expected: describe('orderStatusMapper', () => { it('should return user status processing for unknown status', () => { expect(getCustomerOrderStatus('unknown')).toBe('processing'); }); it('should return user status processing for creaetd', () => { expect(getCustomerOrderStatus('created')).toBe('processing'); }); it('should return user status shipped for out for delivery', () => { expect(getCustomerOrderStatus('out_for_delivery')).toBe('shipped'); }); it('should return user status delivered for out for delivered', () => { expect(getCustomerOrderStatus('delivered')).toBe('delivered'); }); }); As you can see above, the code seems to be very repetitive. Next, you are doing to use Jest each to write the code for all the internal to customer status mapping. Code Example with Jest each # Now, let’s write a test for the above code using each in Jest to make it more readable, concise, and cover all the different test cases: import { getPublicOrderStatus } from '../src/orderStatusMapper.js';const orderStatuses = [ ['unknown', 'processing'], ['created', 'processing'], ['sent_to_warehouse', 'processing'], ['picked_at_warehouse', 'processing'], ['sent_to_courier', 'processing'], ['picked_up_by_courier', 'shipped'], ['out_for_delivery', 'shipped'], ['delivered', 'delivered'],];describe('orderStatusMapper', () => { it.each(orderStatuses)('For %s it should return %s ', (input, expected) => { expect(getPublicOrderStatus(input)).toBe(expected); });}); Here the test cases look much simpler and streamlined. The orderStauses array is taken in as the input for it.each and for each row the test runs once. If you run the test it will give the following output: To make the example complete, next is the same set of tests with the tagged template literals syntax: it.each` input | expected ${'unknown'} | ${'processing'} ${'created'} | ${'processing'} ${'sent_to_warehouse'} | ${'processing'} ${'picked_at_warehouse'} | ${'processing'} ${'sent_to_courier'} | ${'processing'} ${'picked_up_by_courier'} | ${'shipped'} ${'out_for_delivery'} | ${'shipped'} ${'delivered'} | ${'delivered'}`('should return with template - $expected for $input', ({ input, expected }) => { expect(getPublicOrderStatus(input)).toBe(expected);}); It is the same example but formatted differently with template literals. You can also use test.each in place of it.each and it will have the same result. The full code with the test for this example is available in this GitHub repo. You can also check the GitHub Action for the test run. Conclusion # Jest each is a great feature to write concise and easy-to-read tests. It also helps you write data-driven tests with more code coverage with less test code. Make a habit to use each in your Jest tests and I hope it will make you a better software engineer. Keep writing tests!


View Entire Post

Read Entire Article