如何测试@apollo/client中OnSubscriptionData的功能?
onSubscriptionData in Apollo Client Subscriptions Hey there! I’ve been in your shoes before—testing onSubscriptionData in Apollo Client can feel tricky since examples are sparse. Let’s break this down into actionable steps with concrete code examples that you can adapt to your project.
Core Approach
The key idea is to use Apollo’s MockedProvider to simulate subscription events, trigger the onSubscriptionData callback, and then assert that your custom logic (like updating cache, state, or UI) behaves as expected. We’ll use Jest and React Testing Library here, but the concepts translate to other testing frameworks too.
First, Let’s Assume Your Component Code
Say you have a component that uses useSubscription with an onSubscriptionData handler like this:
import { useSubscription } from '@apollo/client'; import { NEW_MESSAGE_SUBSCRIPTION } from './graphql/subscriptions'; import { GET_MESSAGES_QUERY } from './graphql/queries'; export const ChatFeed = () => { const { data } = useSubscription(NEW_MESSAGE_SUBSCRIPTION, { onSubscriptionData: ({ subscriptionData, client }) => { // The logic we want to test: update local cache with new message const existingMessages = client.readQuery({ query: GET_MESSAGES_QUERY }); client.writeQuery({ query: GET_MESSAGES_QUERY, data: { messages: [...existingMessages.messages, subscriptionData.data.newMessage] } }); } }); return <div className="chat-feed">{/* Render messages */}</div>; };
Step-by-Step Test Implementation
1. Set Up MockedProvider and Initial State
We’ll create a mock subscription response and initialize the Apollo cache with some starting data to simulate a real-world scenario.
2. Write the Test for Cache Updates
Here’s how to verify that onSubscriptionData correctly updates the cache:
import { render, waitFor } from '@testing-library/react'; import { MockedProvider, ApolloClient, InMemoryCache } from '@apollo/client'; import { ChatFeed } from './ChatFeed'; import { NEW_MESSAGE_SUBSCRIPTION, GET_MESSAGES_QUERY } from './graphql'; // Initial cache data to simulate existing messages const initialCache = { messages: [{ id: '1', content: 'Hey!' }] }; // Mock subscription response that triggers onSubscriptionData const subscriptionMock = { request: { query: NEW_MESSAGE_SUBSCRIPTION, // Add variables here if your subscription uses them! }, result: { data: { newMessage: { id: '2', content: 'Hi from subscription!' } } } }; test('onSubscriptionData updates cache with new message', async () => { // Create a client with pre-populated cache const client = new ApolloClient({ cache: new InMemoryCache().restore(initialCache), link: jest.fn() // Mock link since we're using MockedProvider }); // Render component wrapped in MockedProvider render( <MockedProvider mocks={[subscriptionMock]} client={client}> <ChatFeed /> </MockedProvider> ); // Wait for the subscription callback to fire and update cache await waitFor(async () => { const cachedMessages = await client.readQuery({ query: GET_MESSAGES_QUERY }); // Assert cache was updated correctly expect(cachedMessages.messages).toHaveLength(2); expect(cachedMessages.messages[1].content).toBe('Hi from subscription!'); }); });
3. Testing UI/State Updates
If your onSubscriptionData updates component state (like showing a notification), adjust the test to check the UI:
// Updated component with notification state export const ChatFeed = () => { const [notification, setNotification] = useState(''); const { data } = useSubscription(NEW_MESSAGE_SUBSCRIPTION, { onSubscriptionData: ({ subscriptionData }) => { setNotification(`New message: ${subscriptionData.data.newMessage.content}`); } }); return ( <div> <div className="notification">{notification}</div> <div className="chat-feed">{/* Render messages */}</div> </div> ); }; // Corresponding test test('onSubscriptionData displays notification', async () => { render( <MockedProvider mocks={[subscriptionMock]}> <ChatFeed /> </MockedProvider> ); await waitFor(() => { const notification = screen.getByText(/New message: Hi from subscription!/i); expect(notification).toBeInTheDocument(); }); });
Testing Edge Cases
Don’t forget to test error scenarios! For example, if the subscription fails:
const errorSubscriptionMock = { request: { query: NEW_MESSAGE_SUBSCRIPTION, }, error: new Error('Subscription connection failed') }; test('onSubscriptionData handles subscription errors', async () => { render( <MockedProvider mocks={[errorSubscriptionMock]}> <ChatFeed /> </MockedProvider> ); await waitFor(() => { const errorText = screen.getByText(/Subscription connection failed/i); expect(errorText).toBeInTheDocument(); }); });
Key Tips
- Match Queries Exactly: Ensure your mock’s
requestmatches the subscription query used in your component (including variables—if your subscription uses them, add them to the mock request!). - Wait for Async Actions: Always use
waitFororfindBy*queries to wait for theonSubscriptionDatacallback to execute, since it’s triggered asynchronously. - Mock Dependencies: If your
onSubscriptionDatarelies on external services or utilities, mock those to isolate the logic you’re testing.
内容的提问来源于stack exchange,提问作者Roei Rom

