Skip to content
intermediate Phase 12 · Testing

Testing Async Code

Test asynchronous operations with proper assertions and timeouts.

30m
0 problems
Topic Progress 0%

Async/Await in Tests

Async/Await in Tests

Basic Async Test

// Async function
describe('fetchUser', () => {
  it('should return user data', async () => {
    const user = await fetchUser(1);
    expect(user).toEqual({ id: 1, name: 'John' });
  });
});

Async Component Test

import { render, screen, waitFor } from '@testing-library/react';

describe('UserProfile', () => {
  it('should display user name after loading', async () => {
    render(<UserProfile id={1} />);

    // Wait for loading to complete
    await waitFor(() => {
      expect(screen.getByText('John')).toBeInTheDocument();
    });
  });

  it('should show error on failure', async () => {
    // Mock API error
    server.use(
      http.get('/api/users/1', () => {
        return HttpResponse.json(null, { status: 500 });
      })
    );

    render(<UserProfile id={1} />);

    await waitFor(() => {
      expect(screen.getByRole('alert')).toHaveTextContent('Error');
    });
  });
});

findBy Query

// findBy waits for element automatically
describe('SearchResults', () => {
  it('should display results', async () => {
    render(<SearchResults query="react" />);

    // findByText waits for element to appear
    const result = await screen.findByText('React Results');
    expect(result).toBeInTheDocument();
  });
});

Timer Mocks

Timer Mocks

Jest Fake Timers

// Use fake timers
beforeEach(() => {
  jest.useFakeTimers();
});

afterEach(() => {
  jest.useRealTimers();
});

describe('debounce', () => {
  it('should delay execution', () => {
    const fn = jest.fn();
    const debounced = debounce(fn, 300);

    debounced();
    expect(fn).not.toHaveBeenCalled();

    // Fast forward time
    jest.advanceTimersByTime(300);
    expect(fn).toHaveBeenCalledTimes(1);
  });
});

Async Timer Tests

describe('delay', () => {
  it('should resolve after delay', async () => {
    const promise = delay(1000);

    jest.advanceTimersByTime(1000);

    await expect(promise).resolves.toBeUndefined();
  });
});

Interval Testing

describe('polling', () => {
  it('should call function repeatedly', () => {
    const fn = jest.fn();
    startPolling(fn, 5000);

    jest.advanceTimersByTime(15000);

    expect(fn).toHaveBeenCalledTimes(3);
  });
});

Date Mocking

// Mock specific date
const mockDate = new Date('2024-01-15T12:00:00');
jest.spyOn(global, 'Date').mockImplementation((arg) => {
  if (arg) return new originalDate(arg);
  return mockDate;
});

// Or use jest.mock
dateSpy.mockReturnValue(mockDate);

Animation Testing

// Mock requestAnimationFrame
window.requestAnimationFrame = jest.fn(cb => setTimeout(cb, 16));
window.cancelAnimationFrame = jest.fn(id => clearTimeout(id));

// Mock CSS animations
Element.prototype.animate = jest.fn(() => ({
  finished: Promise.resolve(),
}));

Network Requests

Network Requests

MSW (Recommended)

import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  http.get('/api/data', () => {
    return HttpResponse.json({ value: 42 });
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

it('should fetch data', async () => {
  render(<DataComponent />);

  await waitFor(() => {
    expect(screen.getByText('42')).toBeInTheDocument();
  });
});

Axios Mock

import axios from 'axios';
jest.mock('axios');

it('should fetch data with axios', async () => {
  axios.get.mockResolvedValueOnce({
    data: { id: 1, name: 'John' },
  });

  const user = await fetchUser(1);

  expect(user).toEqual({ id: 1, name: 'John' });
  expect(axios.get).toHaveBeenCalledWith('/api/users/1');
});

Error Testing

it('should handle network error', async () => {
  server.use(
    http.get('/api/data', () => {
      return HttpResponse.error();
    })
  );

  render(<DataComponent />);

  await waitFor(() => {
    expect(screen.getByText('Network error')).toBeInTheDocument();
  });
});

it('should handle timeout', async () => {
  server.use(
    http.get('/api/data', async () => {
      await delay(5000);
      return HttpResponse.json({ data: 'test' });
    })
  );

  render(<DataComponent />);

  await waitFor(() => {
    expect(screen.getByText('Request timeout')).toBeInTheDocument();
  });
});

Request Verification

it('should send correct request', async () => {
  const handler = http.post('/api/users', async ({ request }) => {
    const body = await request.json();
    expect(body).toEqual({ name: 'John', email: 'john@example.com' });
    return HttpResponse.json({ id: 1, ...body });
  });

  server.use(handler);

  render(<CreateUserForm />);

  await userEvent.type(screen.getByLabelText('Name'), 'John');
  await userEvent.type(screen.getByLabelText('Email'), 'john@example.com');
  await userEvent.click(screen.getByText('Create'));

  await waitFor(() => {
    expect(screen.getByText('User created')).toBeInTheDocument();
  });
});

Quiz

1. How do you test async functions?

Question 1 options

2. What does jest.useFakeTimers do?

Question 2 options

3. What is a common mistake when implementing Testing Async Code?

Question 3 options

Flashcards

Question

How do you test async code?

Answer

Use async/await, waitFor for assertions, and findBy queries for elements.

Question

What are fake timers?

Answer

Mocks for setTimeout/setInterval that let you control time in tests.

Question

How do you test network errors?

Answer

Configure MSW to return error responses and verify error handling.

Question

What is the findBy query?

Answer

An async query that waits for an element to appear in the DOM.

Revision Notes

Key Takeaways

  • 1. Use async/await for clean async tests
  • 2. waitFor retries assertions until timeout
  • 3. Fake timers control setTimeout/setInterval
  • 4. MSW provides realistic API mocking
  • 5. findBy queries wait for elements automatically

Interview Tips

  • Explain how to test async code
  • Discuss fake timers and when to use them
  • Know how to test network requests and errors

Cheat Sheet

Testing Async Cheat Sheet

Async/Await

it('should work', async () => {
  const result = await asyncFunction();
  expect(result).toBe(expected);
});

waitFor

await waitFor(() => {
  expect(screen.getByText('Done')).toBeInTheDocument();
});

Fake Timers

jest.useFakeTimers();
jest.advanceTimersByTime(1000);

MSW

server.use(
  http.get('/api', () => HttpResponse.json(data))
);