Rabi Siddique
828 words
4 minutes
Working with JEST

Jest is a popular JavaScript testing framework that allows you to write unit tests. It comes with a suite of utilities that make testing easier. Here is a typical structure for a Jest test case:

Example Structure of a Jest Test Case#

// Example test case using the mocked dependencies
it('should perform order cancellation', () => {
  // Arrange: Set up the test scenario
  // Act: Perform the action that triggers the unit under test
  // Assert: Validate the outcomes using Jest expectations
});

Setup and Teardown#

In testing, there are often preliminary actions (setup) and concluding actions (teardown) that need to be performed around your tests. Jest provides functions like beforeEach and afterEach for this purpose.

  • Setup (beforeEach): This is about preparing your testing environment before each test runs. For example, if you’re testing functions that rely on a database, you might need to initialize this database first.
beforeEach(() => {
  initializeCityDatabase();
});
  • Teardown (afterEach): This involves cleaning up after each test. This might include actions like clearing a database to ensure one test’s data doesn’t affect another.
afterEach(() => {
  clearCityDatabase();
});

You can find more information in the official Jest documentation over here.

Mocking in Jest#

Mock functions in testing are tools that help you check the interactions between different parts of your code without relying on their actual implementations. Mock functions replace the real functions in your code during testing. They can:

  • Simulate real behavior by returning specific values.
  • Track calls to the function, along with the arguments passed during those calls.
  • Monitor instances when the function is used as a constructor.
  • Control return values during tests to check different scenarios.
// Basic mock function
const mockFunction = jest.fn();

// Mock function with return values
const mockFunctionWithReturn = jest
  .fn()
  .mockReturnValueOnce(10)
  .mockReturnValueOnce('x')
  .mockReturnValue(true);

// Asynchronous mock function
const asyncMock = jest.fn().mockResolvedValue('completed');

// Tests
describe('Mock Function Tests', () => {
  test('basic mock function is called with correct argument', () => {
    mockFunction('hello');
    expect(mockFunction).toHaveBeenCalledWith('hello');
  });

  test('mock function returns specified values correctly', () => {
    expect(mockFunctionWithReturn()).toBe(10); // First call
    expect(mockFunctionWithReturn()).toBe('x'); // Second call
    expect(mockFunctionWithReturn()).toBe(true); // Subsequent calls
  });

  test('asynchronous mock function resolves to expected value', async () => {
    await expect(asyncMock()).resolves.toBe('completed');
  });
});

Explore more information in the official doc over here.

Using spyOn#

The jest.spyOn function is a powerful feature in Jest that lets you create a mock function that also keeps track of calls to a method on an object. This spy function not only monitors how a method is used, but it can also run the original method, unlike other mocking approaches that simply replace the method.

Read more about it from these links:

Checking how many times a function was called#

Use the toHaveBeenCalledTimes method to specify the expected number of times a function should have been invoked during the test. This helps ensure that your function is called the correct number of times.

expect(orderRepository.insertActivityLogs).toHaveBeenCalledTimes(1);

Checking the result of a function call#

To verify the outcome returned by a function, use the expect function followed by a matcher like toBe. This confirms that the function returns the expected result when invoked with specific arguments.

const result = await retailerOrderStrategy.insertCancellationLogs(
  ctx,
  orderIds,
  ordersInfo,
  orderCancellationDto
);
expect(result).toBe(true);

Checking if a function is not called#

To ensure a function was not called with certain arguments, use not.toHaveBeenCalledWith. This is useful for verifying that a function does not execute with specific parameters in certain scenarios.

expect(orderRepository.insertActivityLogs).not.toHaveBeenCalledWith(ctx, [
  {
    payload: 'Out of stock unmarked due cancel order',
    changes: JSON.stringify({ isStock: false }),
    sourceId: orderItems[0].skuId,
  },
  {
    payload: 'Out of stock unmarked due cancel order',
    changes: JSON.stringify({ isStock: false }),
    sourceId: orderItems[1].skuId,
  },
]);

Illustrating the type of values passed/returned by a function#

In the snippet below, we expect a function to be called with a set of parameters and one of the parameters have been specified to be a date:

expect(orderRepository.decrementAndIncrementUpdate).toHaveBeenCalledWith(
  ctx,
  { id: '2' },
  'current_aoos_inv',
  13,
  'aoos_limit',
  { updated_at: expect.any(Date) }
);

A Gotcha with Mock Functions#

When using Jest to track how many times a function is called(using toHaveBeenCalledTimes and toHaveBeenCalledWith) or to check the arguments it was called with, the function in question must be a mock or a spy. If you attempt these checks on a regular function, Jest will throw an error. Here’s the typical error message you’ll see if you mistakenly apply these matchers to a non-mocked function:

expect(received).toHaveBeenCalledTimes(expected)

Matcher error: received value must be a mock or spy function

Received has type:  function
Received has value: [Function validateOrderCancelPayload]

Testing for Specific Errors with Mocked Functions#

When you anticipate that a mocked function should throw an error under certain conditions, it’s beneficial to specify the type or message of the expected error. This not only confirms the presence of an error but also validates that the correct error is thrown, which is crucial for maintaining robust error handling in your application.

// Suppose `cancelOrder` is mocked to throw an error when a cancellation is invalid
jest.spyOn(retailerOrderStrategy, 'cancelOrder').mockImplementation(() => {
  throw new Error('Invalid cancellation attempt');
});

// Test that the specific error is thrown
await expect(
  retailerOrderStrategy.cancelOrder(ctx, orderCancellationDto, type)
).rejects.toThrowError('Invalid cancellation attempt');

Good Reads#

Working with JEST
https://rabisiddique.com/posts/experience-with-jest/
Author
Rabi Siddique
Published at
2023-11-20