Hone logo
Hone
Problems

Asynchronous Operations in Jest: A Real-World Scenario

Testing asynchronous code can be tricky, as it involves dealing with operations that don't complete immediately. This challenge will test your ability to effectively use Jest to test functions that perform asynchronous tasks, such as fetching data from an API or performing time-consuming computations. Mastering this is crucial for building robust and reliable applications.

Problem Description

You are tasked with testing a series of asynchronous functions that simulate real-world operations. Your goal is to write Jest tests that correctly assert the outcomes of these functions, ensuring they behave as expected under various conditions.

Key Requirements:

  1. Simulate API Calls: Create a mock asynchronous function that simulates fetching data from an API. This function should return a Promise that resolves with a specific data structure after a short delay.
  2. Test for Resolved Promises: Write Jest tests to verify that your simulated API call function resolves correctly with the expected data.
  3. Handle Asynchronous Errors: Create another mock asynchronous function that simulates an operation that might fail. This function should return a Promise that rejects with an error under certain conditions.
  4. Test for Rejected Promises: Write Jest tests to verify that your error-simulating function rejects correctly when expected.
  5. Sequential Asynchronous Operations: Implement a function that orchestrates multiple asynchronous operations sequentially and test its final outcome.

Expected Behavior:

  • Your tests should pass if the asynchronous functions behave as described and your assertions accurately reflect their outcomes.
  • Tests for successful resolutions should confirm the returned data.
  • Tests for rejections should confirm that the correct error is thrown.

Edge Cases to Consider:

  • What happens if the simulated API call times out or takes longer than expected? (For this challenge, we'll focus on successful and immediate error handling, but be aware of this for real-world scenarios.)
  • How do you handle multiple asynchronous operations that depend on each other?

Examples

Example 1: Testing a Successful API Call

Imagine a function fetchUserData(userId: number) that simulates fetching user data.

// Function to be tested (conceptually)
async function fetchUserData(userId: number): Promise<{ id: number; name: string }> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: userId, name: `User ${userId}` });
    }, 100); // Simulate network delay
  });
}

Jest Test (Conceptual):

test('should fetch user data successfully', async () => {
  const userData = await fetchUserData(1);
  expect(userData).toEqual({ id: 1, name: 'User 1' });
});

Explanation: The await keyword pauses the test execution until the fetchUserData Promise resolves. The expect assertion then checks if the resolved value matches the expected user data.

Example 2: Testing an Asynchronous Function with Potential Errors

Imagine a function processOrder(orderId: string) that might throw an error if the order is invalid.

// Function to be tested (conceptually)
async function processOrder(orderId: string): Promise<string> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (orderId === 'invalid-order') {
        reject(new Error('Order is invalid'));
      } else {
        resolve(`Order ${orderId} processed successfully`);
      }
    }, 50); // Simulate processing time
  });
}

Jest Test (Conceptual):

test('should reject with an error for an invalid order', async () => {
  await expect(processOrder('invalid-order')).rejects.toThrow('Order is invalid');
});

Explanation: expect(...).rejects.toThrow() is used to assert that a Promise will reject and that the rejection reason is an error with a specific message.

Example 3: Testing Sequential Asynchronous Operations

Imagine a function processPaymentAndNotify(amount: number) that first processes a payment and then sends a notification.

// Mock functions (to be implemented and tested)
async function mockProcessPayment(amount: number): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Payment of $${amount} processed.`);
    }, 150);
  });
}

async function mockSendNotification(message: string): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Notification sent: "${message}"`);
    }, 75);
  });
}

// Function to orchestrate and test
async function processPaymentAndNotify(amount: number): Promise<string> {
  const paymentResult = await mockProcessPayment(amount);
  const notificationResult = await mockSendNotification(paymentResult);
  return notificationResult;
}

Jest Test (Conceptual):

test('should process payment and send notification sequentially', async () => {
  const result = await processPaymentAndNotify(50);
  expect(result).toBe('Notification sent: "Payment of $50 processed."');
});

Explanation: The await keyword ensures that mockProcessPayment completes before mockSendNotification is called. The final assertion checks the combined outcome.

Constraints

  • All asynchronous operations should be simulated using setTimeout within Promises.
  • Delays for setTimeout should be between 50ms and 200ms.
  • Input types will be primitives (numbers, strings) or simple objects.
  • Focus on demonstrating correct Jest asynchronous testing patterns.

Notes

  • Remember to use async/await in your test files when dealing with Promises.
  • Jest provides matchers like resolves and rejects that are specifically designed for testing Promises.
  • You'll need to implement the actual functions to be tested based on the conceptual examples provided. Your task is to write the Jest tests for these functions.
  • Consider how you would mock these functions in a real-world scenario if they were external dependencies. For this challenge, you'll be testing functions that you write yourself.
Loading editor...
typescript