Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MockSubscriptionLink doesn't support multiple subscriptions #12128

Open
artaommahe opened this issue Nov 14, 2024 · 3 comments
Open

MockSubscriptionLink doesn't support multiple subscriptions #12128

artaommahe opened this issue Nov 14, 2024 · 3 comments

Comments

@artaommahe
Copy link

Issue Description

We have a hook that creates 2 subscriptions. Testing it with only one subscription via MockSubscriptionLink was fine, but after adding the second one tests are not working anymore.

Looking at the code of MockSubscriptionLink it looks like it can't deal with multiple subscriptions: only last operation is saved, there is no filtering by subscription in the simulateResult and simulateComplete methods.

Link to Reproduction

Reproduction Steps

No response

@apollo/client version

3.11.9

@phryneas
Copy link
Member

That is indeed a rare use case. I believe the best course of action here would be to use a split link to combine two MockSubscriptionLinks into one "combined mock link".

@artaommahe
Copy link
Author

artaommahe commented Nov 18, 2024

as a workaround for now i've patched the local version of MockSubscriptionLink to support multiple subscriptions, similar to how MockLink works

import { ApolloLink, DocumentNode, FetchResult, Observable, Operation } from '@apollo/client';
import { addTypenameToDocument, print } from '@apollo/client/utilities';

export interface MockedSubscription {
  request: Operation;
}

export interface MockedSubscriptionResult {
  result?: FetchResult;
  error?: Error;
  delay?: number;
}

// https://github.com/apollographql/apollo-client/issues/12128
// added similar stuff from MockLink implementatiotwn
// https://github.com/apollographql/apollo-client/blob/9d6c30697859724d9af532956a3b52ba256def89/src/testing/core/mocking/mockLink.ts#L85
export class MockMultipleSubscriptionLink extends ApolloLink {
  unsubscribers: any[] = [];
  setups: any[] = [];

  private observers: { [key: string]: any[] } = {};

  request(operation: Operation) {
    return new Observable<FetchResult>(observer => {
      this.setups.forEach(x => x());
      const operationKey = requestToKey(operation.query, true);

      if (!this.observers[operationKey]) {
        this.observers[operationKey] = [];
      }

      this.observers[operationKey].push(observer);

      return () => {
        this.unsubscribers.forEach(x => x());
      };
    });
  }

  simulateResult(
    result: MockedSubscriptionResult,
    { complete = false, document }: { complete?: boolean; document?: DocumentNode } = {},
  ) {
    setTimeout(() => {
      const observers = this.getDocumentObservers(document);

      if (!observers.length) throw new Error('subscription torn down');

      observers.forEach(observer => {
        if (result.result && observer.next) observer.next(result.result);
        if (result.error && observer.error) observer.error(result.error);
        if (complete && observer.complete) observer.complete();
      });
    }, result.delay || 0);
  }

  simulateComplete({ document }: { document?: DocumentNode }) {
    const observers = this.getDocumentObservers(document);

    if (!observers.length) throw new Error('subscription torn down');

    observers.forEach(observer => {
      if (observer.complete) observer.complete();
    });
  }

  onSetup(listener: any): void {
    this.setups = this.setups.concat([listener]);
  }

  onUnsubscribe(listener: any): void {
    this.unsubscribers = this.unsubscribers.concat([listener]);
  }

  private getDocumentObservers(document?: DocumentNode): any[] {
    const operationKey = document ? requestToKey(document, true) : Object.keys(this.observers)[0];
    return this.observers[operationKey];
  }
}

function requestToKey(document: DocumentNode, addTypename: Boolean): string {
  const queryString = document && print(addTypename ? addTypenameToDocument(document) : document);
  const requestKey = { query: queryString };
  return JSON.stringify(requestKey);
}

export function mockObservableLink(): MockMultipleSubscriptionLink {
  return new MockMultipleSubscriptionLink();
}

@ldeveber
Copy link

That is indeed a rare use case.

I would say its not entirely that rare. We have 2-3 subscriptions on one page (one for create, one for update, and a third for async operations that is turned on and off as needed) and we're using split links in our tests. Most of our pages don't have any subscriptions at all though.

I personally would love to see more documentation around testing strategy and mocking subscription/fragments! Especially with the new fragment masking! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants