apollo-feature-requests icon indicating copy to clipboard operation
apollo-feature-requests copied to clipboard

Optional loading state skipping for mocked requests

Open guillermoparral1995 opened this issue 2 years ago โ€ข 5 comments

Motivation

In our project we have setup visual tests of screens that relied on Apollo for fetching information before rendering. These requests were being mocked by setting a MockLink with mocked responses, but we noticed that visual tests were failing because the screen capture was being made in a transitional state between loading and loaded states, which for our use case was undesired.

This solution was inspired by reading about a similar situation in StackOverflow

Proposed solution

We noticed that the issue was inside MockLink's request function, where the actual returning of the mocked responses is wrapped inside a setTimeout function, which forces a loading state even when the mock has explicitly specified a delay of 0. In our project we have copied the MockLink implementation by copying the project and modifying the offending section with the following:

    const handleObserver = (
      observer: ZenObservable.SubscriptionObserver<
        FetchResult<
          {
            [key: string]: any
          },
          Record<string, any>,
          Record<string, any>
        >
      >
    ) => {
      if (configError) {
        try {
          // The onError function can return false to indicate that
          // configError need not be passed to observer.error. For
          // example, the default implementation of onError calls
          // observer.error(configError) and then returns false to
          // prevent this extra (harmless) observer.error call.
          if (this.onError(configError, observer) !== false) {
            throw configError
          }
        } catch (error) {
          observer.error(error)
        }
      } else if (response) {
        if (response.error) {
          observer.error(response.error)
        } else {
          if (response.result) {
            observer.next(
              typeof response.result === 'function'
                ? (response.result as ResultFunction<FetchResult>)()
                : response.result
            )
          }
          observer.complete()
        }
      }
    }

    return new Observable((observer) => {
      let timer: ReturnType<typeof setTimeout> | undefined
      // we keep the original implementation if delay is explicitly set
      if (response?.delay) {
        timer = setTimeout(() => {
          handleObserver(observer)
        }, response.delay)
      } else {
        // it no delay is set, then we directly return the mocked responses without setTimeout
        handleObserver(observer)
      }

      return () => {
        if (timer) clearTimeout(timer)
      }
    })

guillermoparral1995 avatar May 03 '23 14:05 guillermoparral1995