Bài 20.3: Testing hooks và context

Chào mừng bạn đến với bài viết chuyên sâu về kiểm thử (testing) trong React, tập trung vào hai khía cạnh cực kỳ quan trọngthường gặp trong các ứng dụng hiện đại: React HooksContext API. Việc kiểm thử các thành phần này giúp bạn xây dựng ứng dụng mạnh mẽ hơn, ít lỗi hơndễ dàng bảo trì hơn theo thời gian.

Chúng ta sẽ sử dụng React Testing Library (RTL), thư viện kiểm thử được khuyến khích bởi đội ngũ React, với triết lý tập trung vào cách người dùng tương tác với ứng dụng của bạn.

Tại sao phải kiểm thử Hooks và Context?

  • Hooks: Hooks là các hàm cho phép bạn "móc nối" vào các tính năng của React state và lifecycle từ các component hàm. Chúng thường chứa đựng logic xử lý state, side effects (như gọi API, quản lý subscriptions), hoặc encapsulate các behavior phức tạp. Kiểm thử hooks độc lập giúp bạn đảm bảo logic đó hoạt động chính xác mà không cần render toàn bộ component chứa nó. Điều này giúp isolate lỗi và làm unit test ngắn gọn hơn.
  • Context API: Context là một cách để chia sẻ dữ liệu (state, hàm,...) qua cây component mà không cần truyền props xuống thủ công qua mọi cấp. Nó là trái tim của việc quản lý state global đơn giản hoặc inject dependencies (như theme, user info). Việc kiểm thử Context đảm bảo rằng các component tiêu thụ (consume) dữ liệu từ Context một cách đúng đắn và các component cung cấp (provide) dữ liệu hoạt động như mong đợi.

Việc kết hợp kiểm thử hooks và context mang lại sự tự tin rằng các phần cốt lõi, quản lý state và logic chia sẻ của ứng dụng bạn đang hoạt động chính xác.

Kiểm thử React Hooks với renderHook

React Testing Library cung cấp một tiện ích đặc biệt cho việc kiểm thử hooks: @testing-library/react-hooks. Thư viện này cung cấp hàm renderHook, cho phép bạn "render" một hook trong một môi trường React thử nghiệm mà không cần một component DOM thực sự.

Cú pháp cơ bản:

import { renderHook, act } from '@testing-library/react-hooks';

// ... hook cần test ...

test('tên bài kiểm thử hook', () => {
  // Render hook
  const { result, rerender, unmount } = renderHook(() => tenHook(propsBanDau));

  // result.current chứa giá trị trả về hiện tại của hook
  // ... kiểm tra state ban đầu ...

  // act() được sử dụng để wrap các hành động thay đổi state/gây side effects
  act(() => {
    // Gọi hàm từ hook để thay đổi state
    result.current[1](giaTriMoi); // Ví dụ cho useState [state, setState]
  });

  // ... kiểm tra state sau khi thay đổi ...

  // rerender(newProps) để test khi props thay đổi
  // unmount() để test cleanup effects (useEffect)
});

Hãy xem xét một vài ví dụ cụ thể.

Ví dụ 1: Kiểm thử hook quản lý state đơn giản (useCounter)

Giả sử bạn có một hook đơn giản để quản lý bộ đếm:

// hooks/useCounter.js
import { useState, useCallback } from 'react';

const useCounter = (initialValue = 0) => {
  const [count, setCount] = useState(initialValue);

  const increment = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(prevCount => prevCount - 1);
  }, []);

  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]); // initialValue là dependency

  return { count, increment, decrement, reset };
};

export default useCounter;

Đây là cách bạn kiểm thử nó:

// hooks/useCounter.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';

test('useCounter should initialize with default or provided value', () => {
  // Test default value
  const { result: resultDefault } = renderHook(() => useCounter());
  expect(resultDefault.current.count).toBe(0);

  // Test provided initial value
  const { result: resultInitial } = renderHook(() => useCounter(10));
  expect(resultInitial.current.count).toBe(10);
});

test('useCounter should increment the count', () => {
  const { result } = renderHook(() => useCounter(5)); // Start from 5

  expect(result.current.count).toBe(5);

  // Use act() for state updates
  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(6); // Check after increment

  act(() => {
    result.current.increment();
    result.current.increment();
  });

  expect(result.current.count).toBe(8); // Check after multiple increments
});

test('useCounter should decrement the count', () => {
  const { result } = renderHook(() => useCounter(5));

  act(() => {
    result.current.decrement();
  });

  expect(result.current.count).toBe(4);

  act(() => {
    result.current.decrement();
    result.current.decrement();
  });

  expect(result.current.count).toBe(2);
});

test('useCounter should reset the count to initial value', () => {
  const { result } = renderHook(() => useCounter(10));

  act(() => {
    result.current.increment();
    result.current.increment();
  });
  expect(result.current.count).toBe(12); // Change the state

  act(() => {
    result.current.reset();
  });

  expect(result.current.count).toBe(10); // Reset to initial (10)
});

test('useCounter should reset to new initial value if props change', () => {
    const { result, rerender } = renderHook(({ initialValue }) => useCounter(initialValue), {
      initialProps: { initialValue: 0 },
    });

    expect(result.current.count).toBe(0);

    act(() => {
      result.current.increment();
    });
    expect(result.current.count).toBe(1);

    // Rerender with new initialValue
    rerender({ initialValue: 100 });

    // The counter state is preserved by default
    expect(result.current.count).toBe(1);

    // Reset should now use the new initialValue
    act(() => {
      result.current.reset();
    });
    expect(result.current.count).toBe(100);
});

Giải thích code test:

  • Chúng ta import renderHookact từ @testing-library/react-hooks.
  • renderHook(() => useCounter()): Gọi hook cần test bên trong hàm callback. result.current truy cập vào giá trị trả về của hook ({ count, increment, decrement, reset }).
  • expect(result.current.count).toBe(...): Kiểm tra giá trị state hiện tại.
  • act(() => { ... }): Mọi thao tác gây ra thay đổi state hoặc side effects trong React (như gọi increment()) phải được bao bọc bởi act(). Điều này đảm bảo React xử lý xong các bản cập nhật trước khi bạn thực hiện các assertion.
  • rerender({ initialValue: 100 }): Minh họa cách test hook khi props đầu vào của nó thay đổi. useCounter nhận initialValue như một "props". Khi rerender được gọi với props mới, hook sẽ được chạy lại với props đó. Lưu ý rằng state không tự động reset khi props thay đổi, trừ khi logic hook của bạn xử lý điều đó (như hàm reset của chúng ta dựa vào initialValue).
Ví dụ 2: Kiểm thử Hook với Side Effects và Async Logic (useFetch)

Kiểm thử hooks có useEffect hoặc chứa logic bất đồng bộ (async/await) đòi hỏi thêm một chút công sức, thường sử dụng waitFor.

Giả sử bạn có một hook đơn giản để fetch dữ liệu:

// hooks/useFetch.js
import { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      setError(null); // Reset error on new fetch
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    if (url) { // Only fetch if url is provided
       fetchData();
    } else {
       setLoading(false); // If no URL, not loading
       setData(null);
       setError(null);
    }


  }, [url]); // Re-run effect if URL changes

  return { data, loading, error };
};

export default useFetch;

Để kiểm thử hook này, bạn cần mock hàm fetch toàn cục hoặc sử dụng các thư viện mock network như msw (Mock Service Worker). Với ví dụ đơn giản, chúng ta có thể mock fetch trực tiếp.

// hooks/useFetch.test.js
import { renderHook, waitFor } from '@testing-library/react-hooks';
import useFetch from './useFetch';

// Mock the global fetch function
const mockFetch = (data, ok = true, status = 200) =>
  jest.fn(() =>
    Promise.resolve({
      ok,
      status,
      json: () => Promise.resolve(data),
    })
  );

const mockErrorFetch = (error, ok = false, status = 500) =>
  jest.fn(() =>
    Promise.resolve({
      ok,
      status,
      json: () => Promise.reject(error), // Or handle error body if needed
    })
  );


describe('useFetch', () => {
  beforeEach(() => {
    // Reset mocks before each test
    global.fetch = jest.fn();
  });

  afterEach(() => {
    // Clean up mocks
    jest.resetAllMocks();
  });

  test('should fetch data successfully', async () => {
    const dummyData = { id: 1, name: 'Test Item' };
    global.fetch = mockFetch(dummyData); // Set up the mock

    const url = 'https://example.com/api/items/1';
    const { result, rerender } = renderHook(() => useFetch(url));

    // Initial state
    expect(result.current.loading).toBe(true);
    expect(result.current.data).toBeNull();
    expect(result.current.error).toBeNull();

    // Wait for the async operation (fetch) to complete and state to update
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
      expect(result.current.data).toEqual(dummyData);
      expect(result.current.error).toBeNull();
    });

    // Verify fetch was called with the correct URL
    expect(global.fetch).toHaveBeenCalledWith(url);
    expect(global.fetch).toHaveBeenCalledTimes(1);

    // Test fetching with a new URL
    const dummyData2 = { id: 2, name: 'Another Item' };
    global.fetch = mockFetch(dummyData2); // Set up mock for the new fetch
    const newUrl = 'https://example.com/api/items/2';

    // Rerender hook with new URL, which should trigger new effect
    rerender(newUrl);

    // State should go back to loading initially (depending on hook logic)
    expect(result.current.loading).toBe(true);
    expect(result.current.data).toBeNull(); // Data might reset or not, depends on hook impl.

    // Wait for the second fetch to complete
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
      expect(result.current.data).toEqual(dummyData2);
      expect(result.current.error).toBeNull();
    });

    // Verify fetch was called again with the new URL
    expect(global.fetch).toHaveBeenCalledWith(newUrl);
    expect(global.fetch).toHaveBeenCalledTimes(2); // Total calls

  });

  test('should handle fetch error', async () => {
    const fetchError = new Error('Failed to fetch');
    global.fetch = mockErrorFetch(fetchError); // Set up error mock

    const url = 'https://example.com/api/error';
    const { result } = renderHook(() => useFetch(url));

    // Initial state
    expect(result.current.loading).toBe(true);
    expect(result.current.data).toBeNull();
    expect(result.current.error).toBeNull();

    // Wait for the async operation to complete (and fail)
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
      expect(result.current.data).toBeNull();
      expect(result.current.error).toEqual(expect.any(Error)); // Check if error is an Error object
      // Or specific error message if needed: expect(result.current.error.message).toBe('Failed to fetch');
    });

    expect(global.fetch).toHaveBeenCalledWith(url);
    expect(global.fetch).toHaveBeenCalledTimes(1);
  });

   test('should not fetch if url is null or undefined', () => {
      const { result } = renderHook(() => useFetch(null)); // Test with null URL

      expect(global.fetch).not.toHaveBeenCalled(); // Fetch should not be called
      expect(result.current.loading).toBe(false); // Should not be loading
      expect(result.current.data).toBeNull();
      expect(result.current.error).toBeNull();
   });
});

Giải thích code test:

  • Chúng ta mock hàm fetch toàn cục bằng jest.fn(). mockFetchmockErrorFetch là các hàm helper để tạo ra các mock response/reject.
  • await waitFor(() => { ... }): Đây là hàm quan trọng nhất khi test logic bất đồng bộ. Nó đợi cho đến khi hàm callback bên trong không còn ném lỗi nữa (hoặc timeout). Chúng ta sử dụng nó để chờ đợi fetch hoàn thành và hook cập nhật state (loading -> false, data/error được set).
  • Kiểm tra state ban đầu (loading: true, data/error: null).
  • Kiểm tra state sau khi waitFor hoàn thành (dữ liệu đã có hoặc lỗi đã được set, loading: false).
  • Sử dụng expect(global.fetch).toHaveBeenCalledWith(...)toHaveBeenCalledTimes(...) để xác minh rằng hàm fetch đã được gọi đúng số lần với đúng URL.
  • Bài test thứ hai minh họa cách xử lý trường hợp lỗi khi fetch.
  • Bài test thứ ba kiểm tra điều kiện không gọi fetch nếu URL không hợp lệ.
  • rerender(newUrl) trong bài test đầu tiên cho thấy cách kiểm thử khi dependency của useEffect thay đổi.

Kiểm thử các hooks phức tạp hơn (với useReducer, useCallback, useMemo, tương tác DOM với useRefuseEffect) sẽ tuân theo các nguyên tắc tương tự: renderHook, sử dụng act cho các hành động gây thay đổi state/effects, và sử dụng waitFor cho logic bất đồng bộ.

Kiểm thử Context API

Kiểm thử Context API thường liên quan đến việc kiểm thử các component hoặc hooks sử dụng Context. RTL tập trung vào kiểm thử cách người dùng tương tác với giao diện, do đó, khi kiểm thử Context, chúng ta thường render các component bao bọc bởi Provider cần thiết.

Ví dụ 3: Kiểm thử Component Tiêu Thụ Context (Consumer)

Giả sử bạn có UserContextUserProvider như đã giới thiệu ở phần trên:

// UserContext.js (Lặp lại để tiện theo dõi)
import React, { createContext, useContext, useState } from 'react';

const UserContext = createContext({ name: 'Guest', setName: () => {} });

const UserProvider = ({ children }) => {
  const [name, setName] = useState('Default User'); // Giá trị mặc định thực tế
  return (
    <UserContext.Provider value={{ name, setName }}>
      {children}
    </UserContext.Provider>
  );
};

const useUser = () => useContext(UserContext);

export { UserContext, UserProvider, useUser };

// MyComponent.js (Component tiêu thụ Context)
import React from 'react';
import { useUser } from './UserContext';

const MyComponent = () => {
  const { name, setName } = useUser(); // Lấy name và setName từ context

  return (
    <div>
      <h1>Xin chào, {name}!</h1>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Nhập tên của bạn"
      />
    </div>
  );
};

export default MyComponent;

Để test MyComponent, nó cần được đặt bên trong một UserProvider.

// MyComponent.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
import { UserProvider } from './UserContext'; // Import the real provider

test('MyComponent displays default user name from Provider and updates on input', async () => {
  render(
    // Wrap the component with the necessary Provider
    <UserProvider>
      <MyComponent />
    </UserProvider>
  );

  // Check if the default name from Provider is displayed initially
  expect(screen.getByText('Xin chào, Default User!')).toBeInTheDocument();
  const inputElement = screen.getByPlaceholderText('Nhập tên của bạn');
  expect(inputElement).toHaveValue('Default User'); // Input reflects context state

  // Simulate user typing into the input
  const newName = 'Alice';
  fireEvent.change(inputElement, { target: { value: newName } });

  // Wait for state update and re-render (fireEvent is usually wrapped in act internally by RTL)
   // We can directly check the updated state shown in the UI
  expect(screen.getByText(`Xin chào, ${newName}!`)).toBeInTheDocument();
  expect(inputElement).toHaveValue(newName);
});

test('MyComponent uses default context value if no Provider is present (not recommended)', () => {
  // Test without wrapping in Provider - relies on the default value in createContext
  // This test is mainly for understanding, typically you'd ensure Provider is always used
  render(<MyComponent />);

  // Check if the default value from createContext is used
  // Note: The default value in createContext is used *only* when the component
  // is rendered *outside* of a matching Provider.
  expect(screen.getByText('Xin chào, Guest!')).toBeInTheDocument(); // 'Guest' is the default in createContext
  const inputElement = screen.getByPlaceholderText('Nhập tên của bạn');
  expect(inputElement).toHaveValue('Guest'); // Input reflects context state
  // Cannot test state updates easily here as the default context value is static.
});


// Alternative: Mocking Context Value
test('MyComponent displays mocked user name when using a mocked Provider wrapper', () => {
    // This is useful if the real Provider is complex or has side effects.
    // We create a simple mock wrapper that provides specific context values.
    const MockUserProvider = ({ children }) => {
        const mockValue = { name: 'Mock User', setName: jest.fn() };
        return (
          <UserContext.Provider value={mockValue}>
            {children}
          </UserContext.Provider>
        );
    };

    render(
        <MockUserProvider>
            <MyComponent />
        </MockUserProvider>
    );

    expect(screen.getByText('Xin chào, Mock User!')).toBeInTheDocument();
    const inputElement = screen.getByPlaceholderText('Nhập tên của bạn');
    expect(inputElement).toHaveValue('Mock User');

    // We can even test if the mocked setName function was called
    const setNameMock = screen.getByText('Xin chào, Mock User!').closest('div').querySelector('input')._wrapperState.initialValue.setName; // This is a bit hacky way to get the mock function in testing. A better way is to pass the mock value directly in render.
    // Let's use a cleaner approach by passing the mock value to the render context directly if needed,
    // or simply testing the output based on the mocked value. Testing the mock itself is less common
    // unless you're testing the Provider's logic. The main goal here is testing the *consumer*.

    // Cleaner way to test with mocked context value:
    render(
      <UserContext.Provider value={{ name: 'Another Mock', setName: jest.fn() }}>
         <MyComponent />
      </UserContext.Provider>
    );
    expect(screen.getByText('Xin chào, Another Mock!')).toBeInTheDocument();
});

Giải thích code test:

  • Import render, screen, fireEvent từ @testing-library/react.
  • Import MyComponentUserProvider.
  • render(<UserProvider><MyComponent /></UserProvider>): Render MyComponent bên trong UserProvider. Đây là cách RTL kiểm thử component sử dụng context: đảm bảo môi trường runtime giống với khi nó chạy trong ứng dụng thật.
  • screen.getByText(...)expect(...).toBeInTheDocument(): Kiểm tra xem component có hiển thị nội dung đúng dựa trên giá trị context hay không.
  • fireEvent.change(...): Mô phỏng hành động của người dùng (gõ vào input). RTL đảm bảo các sự kiện này được wrap trong act.
  • Kiểm tra lại UI sau khi sự kiện xảy ra để xác nhận context đã được cập nhật và component re-render đúng.
  • Bài test thứ hai cho thấy điều gì xảy ra nếu component không được wrap bởi Provider (nó sẽ sử dụng giá trị defaultValue trong createContext). Thường bạn sẽ muốn viết test để đảm bảo component thông báo lỗi hoặc hiển thị trạng thái loading/error nếu Provider bị thiếu, thay vì dựa vào defaultValue.
  • Bài test thứ ba minh họa cách tạo một mock Provider đơn giản hoặc sử dụng trực tiếp Context.Provider với một value được mock. Điều này hữu ích khi Provider thật phức tạp hoặc bạn muốn kiểm soát chính xác giá trị context trong test case cụ thể.
Ví dụ 4: Kiểm thử Hook Sử Dụng Context

Như đã thấy ở trên, chúng ta có hook useUser đơn giản chỉ là wrapper quanh useContext(UserContext). Để kiểm thử hook này, chúng ta cần sử dụng renderHook nhưng cung cấp Context cho nó bằng tùy chọn wrapper.

// hooks/useUser.test.js (Kiểm thử hook useUser)
import { renderHook, act } from '@testing-library/react-hooks';
import { useUser, UserProvider, UserContext } from './UserContext'; // Import hook VÀ Provider/Context

test('useUser hook returns context value when wrapped with Provider', () => {
  // Sử dụng UserProvider thực tế làm wrapper cho renderHook
  const { result } = renderHook(() => useUser(), {
    wrapper: UserProvider, // Chỉ định Provider làm wrapper
  });

  // Kiểm tra giá trị ban đầu được cung cấp bởi UserProvider
  const { name, setName } = result.current;
  expect(name).toBe('Default User');
  expect(typeof setName).toBe('function');
});

test('useUser hook throws error when used outside of Provider', () => {
    // Jest will capture console.error for expected React warnings/errors
    const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

    // renderHook does not automatically wrap in act if there's an error
    // We expect the hook to throw when useContext cannot find a Provider
    expect(() => {
       renderHook(() => useUser()); // Render without wrapper
    }).toThrow(/useContext can only be called inside of the top level of a React function component or custom Hook./);
    // The error message might vary slightly depending on React/Jest version.
    // It might also be a different error if you used createContext(null) initially.

    consoleErrorSpy.mockRestore(); // Restore console.error
});


test('useUser hook returns mocked context value when using a mocked Provider wrapper', () => {
    const mockValue = { name: 'Mocked Name', setName: jest.fn() };

    // Create a simple wrapper using the Context.Provider directly
    const MockWrapper = ({ children }) => (
        <UserContext.Provider value={mockValue}>
            {children}
        </UserContext.Provider>
    );

    const { result } = renderHook(() => useUser(), {
        wrapper: MockWrapper, // Sử dụng wrapper mock
    });

    // Kiểm tra giá trị được trả về từ mock value
    const { name, setName } = result.current;
    expect(name).toBe('Mocked Name');
    expect(setName).toBe(mockValue.setName); // Kiểm tra xem có phải cùng function mock không

    // Optional: Test calling the function returned by the hook
    act(() => {
      setName('New Name'); // Call the mocked function
    });

    expect(mockValue.setName).toHaveBeenCalledWith('New Name'); // Verify the mock function was called
});

Giải thích code test:

  • renderHook(() => useUser(), { wrapper: UserProvider }): Sử dụng tùy chọn wrapper của renderHook để bao bọc việc render hook useUser bằng UserProvider. Điều này giả lập môi trường mà hook sẽ chạy trong ứng dụng thật.
  • Bài test đầu tiên kiểm tra xem hook có lấy được giá trị mặc định từ UserProvider hay không.
  • Bài test thứ hai kiểm tra điều gì xảy ra nếu useUser được gọi không có Provider (nó sẽ ném lỗi). Đây là một test quan trọng để đảm bảo hook của bạn được sử dụng đúng cách hoặc để kiểm thử xử lý lỗi nếu Provider bị thiếu.
  • Bài test thứ ba cho thấy cách tạo một wrapper đơn giản sử dụng trực tiếp UserContext.Provider với một value được mock. Điều này cho phép bạn kiểm soát chính xác giá trị context mà hook nhận được trong quá trình test. Bạn có thể mock cả các hàm trong context và kiểm tra xem chúng có được gọi hay không.
Ví dụ 5: Kiểm thử Provider Component

Đôi khi, bạn cũng muốn kiểm thử chính Provider component, đặc biệt nếu nó có logic phức tạp hơn, ví dụ như quản lý state nội bộ hoặc tương tác với bên ngoài.

// UserContext.test.js (Kiểm thử Provider)
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { UserProvider, UserContext } from './UserContext'; // Import Provider VÀ Context

test('UserProvider provides initial value and updates it via the provided function', async () => {
    // Để kiểm thử Provider, chúng ta cần một cách để "nhìn vào" giá trị context được cung cấp.
    // Một cách là render một component con đơn giản chỉ hiển thị giá trị context.
    const ConsumerComponent = () => {
        const { name, setName } = React.useContext(UserContext);
        return (
            <div>
                <span data-testid="user-name">{name}</span>
                <button onClick={() => setName('Updated User')}>Update Name</button>
            </div>
        );
    };

    render(
        <UserProvider>
            <ConsumerComponent />
        </UserProvider>
    );

    // Check initial state provided by UserProvider
    expect(screen.getByTestId('user-name')).toHaveTextContent('Default User');
    const updateButton = screen.getByText('Update Name');

    // Simulate user clicking the button to update state via the context function
    fireEvent.click(updateButton);

    // Wait for state update and re-render
    await screen.findByText('Updated User'); // Wait until 'Updated User' appears

    // Check the updated state reflected in the UI
    expect(screen.getByTestId('user-name')).toHaveTextContent('Updated User');
});

Giải thích code test:

  • Chúng ta cần một ConsumerComponent nhỏ gọn bên trong bài test để có thể truy cập và hiển thị giá trị từ Context Provider. Component này hoạt động như một cửa sổ để chúng ta quan sát state bên trong Provider.
  • Render ConsumerComponent được wrap bởi UserProvider.
  • Kiểm tra nội dung ban đầu của ConsumerComponent để xác nhận giá trị mặc định được cung cấp bởi UserProvider.
  • Sử dụng fireEvent để mô phỏng tương tác gọi hàm setName được cung cấp qua context.
  • Sử dụng await screen.findByText(...) hoặc waitFor để đợi cho đến khi UI cập nhật phản ánh sự thay đổi state bên trong Provider do hàm setName gây ra.
  • Kiểm tra lại nội dung UI để xác nhận Provider đã cập nhật state nội bộ và cung cấp giá trị mới qua context.

Comments

There are no comments at the moment.