morrys / react-relay-offline

TypeScript library files for Relay Modern Offline
https://morrys.github.io/react-relay-offline/docs/react-relay-offline.html
MIT License
224 stars 15 forks source link

Offline equivalent of createMockEnvironment #103

Closed matt-dalton closed 2 years ago

matt-dalton commented 2 years ago

Hi morrys!

I'm trying to unit test using this kind of approach on Relay v11.0.2, relay-offline v4.0.0

I am fetching like this:

    const isRehydrated = useRestore(environment)

    const qrOptions = {
        fetchPolicy,
        ttl,
        networkCacheConfig: options.cacheConfig
    }

    const { data, error: _error, retry, isLoading: relayLoading } = useQuery(
        options.query,
        variables,
        qrOptions
    )

    if (!isRehydrated || !data) {
        return <Loading />
    }

    return <Component {...data}/>

Typically I would pass in environment={createMockEnvironment()} here, but if I do that it fails because I'm missing offline-specific functions: TypeError: environment.isRehydrated is not a function

If I then try and mock these in a simple way (e.g. returning true for isRehydrated and isOffline) the data returns null.

I saw you had a mock in the codebase here, but this seems to have a lot of complexity I'd rather not replicate.

Is there anything simple I can add as a wrapper around createMockEnvironment that would allow me to fetch data in my tests?

matt-dalton commented 2 years ago

...I should clarify....I don't need to unit test all the functionality of the lib (e.g. offline / online conditions etc) - I just want to test how my components behave when I return data. So I just need useQuery to return some data basically.

morrys commented 2 years ago

Hi @matt-dalton, at the moment to do some offline tests the only way is to copy the logic of the official relay mock https://github.com/facebook/relay/blob/main/packages/relay-test-utils/RelayModernMockEnvironment.js and replace the environment (as I did in this repository).

This is because it is not allowed to pass a custom environment.

matt-dalton commented 2 years ago

OK makes sense. Is there any reason not to just expose createMockEnvironment from your module like the main lib does? Then we could just use your maintained version itself

morrys commented 2 years ago

@matt-dalton, I find the idea interesting, I just changed the PR in wora in order to add these utilities.

Tomorrow I hope to have time to release the first release candidates.

https://github.com/morrys/react-relay-offline/pull/102

morrys commented 2 years ago

Hi @matt-dalton, i released react-relay-offline 5.0.0-rc.1, do you have the chance to try it? 👍

matt-dalton commented 2 years ago

Hi @morrys ...I just tried upgrading to test. I can't see how to import createMockEnvironment - is there a different way I should access this?

morrys commented 2 years ago

import {createMockEnvironment} from 'react-relay-offline/test';

matt-dalton commented 2 years ago

Ah OK....missed the /test

I upgrade to react-relay-offline: 5.0.0-rc.1 and react-relay: 12.0.0 and get this error:

Screenshot 2021-11-29 at 19 02 15

It could be I've got the wrong version of one of your sub-dependencies? I did try clearing node_modules and reinstalling though.

morrys commented 2 years ago

add as dependency dev relay-test-utils-internal v12

matt-dalton commented 2 years ago

Hmmm....I think I'm still missing something. I've got this test file:

describe('QueryRenderer', () => {
    const setup = () => {
        const environment =createMockEnvironment()

        // This just uses the logic in my initial post above
        const Component = withQueryRenderer({
            query: TestQuery
        })(TestComponent)

        const renderer = render(
            <RelayEnvironmentProvider environment={environment}>
                <Component />
            </RelayEnvironmentProvider>
        )

        act(() => {
            environment.mock.resolveMostRecentOperation(operation =>
                MockPayloadGenerator.generate(operation)
            )
        })

        return {
            renderer
        }
    }

    it('should render the component correctly', () => {
        const { renderer } = setup()
        renderer.debug()
    })
})

useQuery still returns null for the data. I've tried:

Am I missing something here? I don't need to test online/offline nuances, I just want to mock the query responses like you can with the raw Relay lib.

morrys commented 2 years ago

it would be useful to create a sample project in which to check for the error...

matt-dalton commented 2 years ago

Do you have an example of a test that works? Perhaps I can try that first.

morrys commented 2 years ago

You can see the tests of this repository

matt-dalton commented 2 years ago

I've managed to narrow down the problem...

I've used your React web todo-updater example to recreate the same test I have in my codebase. I got the test to work, and the logic I posted renders 3 times before returning data: Screenshot 2021-12-21 at 15 42 19

However, in my real codebase (which uses React Native), I just get one render: Screenshot 2021-12-21 at 15 44 52 If I log inside useRestore, I can see that environment.hydrate() never completes.

Is there anything specific to React Native that would prevent this hydrate function from working in tests? e.g. Do I need to mock anything in particular from async storage (I'm currently using the default mock)? Anything else I can log that might help debug?

For reference the working React test file is:

import TestComponent from '../TestComponent';
import {RelayEnvironmentProvider} from 'react-relay';
import {MockPayloadGenerator} from 'relay-test-utils';
import {act, render, waitFor, screen} from '@testing-library/react';
import environment from '../../relay';

describe('TestComponent', () => {
  const setup = () => {
    const renderer = render(
      <RelayEnvironmentProvider environment={environment}>
        <TestComponent />
      </RelayEnvironmentProvider>,
    );

    act(() => {
      environment.mock.queueOperationResolver(operation =>
        MockPayloadGenerator.generate(operation, {
          Query: () => ({
            user: {
              totalCount: 123,
            },
          }),
        }),
      );
    });

    return {
      renderer,
    };
  };

  it('should render the component correctly', async () => {
    const {renderer} = setup();

    await waitFor(() => renderer.getByTestId('text-read'));

    expect(screen.getByText('Total count is: 123')).toBeTruthy();
  });
});
morrys commented 2 years ago

You might see what happens here: https://github.com/morrys/wora/blob/master/packages/offline-first/src/index.ts#L97

Also, here you find an example project in react-native, I don't know if you have time to adapt it to the latest version of react-relay-offline and create a test so as to show me the error

matt-dalton commented 2 years ago

Thanks for the pointer @morrys - that file gave me a way of debugging the problem.

Looks like it works now I've mocked netinfo using the latest netinfo version's mock:

// Copied from later version of netinfo: https://github.com/react-native-netinfo/react-native-netinfo/blob/master/jest/netinfo-mock.js
// Can use lib mock once we (react-relay-offline) upgrades to the later version

const defaultState = {
    type: 'cellular',
    isConnected: true,
    isInternetReachable: true,
    details: {
        isConnectionExpensive: true,
        cellularGeneration: '3g'
    }
}

const RNCNetInfoMock = {
    configure: jest.fn(),
    fetch: jest.fn(),
    addEventListener: jest.fn(),
    useNetInfo: jest.fn()
}

RNCNetInfoMock.fetch.mockResolvedValue(defaultState)
RNCNetInfoMock.useNetInfo.mockReturnValue(defaultState)
RNCNetInfoMock.addEventListener.mockReturnValue(jest.fn())

export default RNCNetInfoMock

I think basically your lib was using an earlier netinfo version (or my project didn't resolve to the latest). Using the now recommended approach above fixed the problem.

Thanks for the help!

matt-dalton commented 2 years ago

Is there a possibility of rolling out your mock as a patch for v4 (Relay v11)? That would allow us to use it ahead of the Relay 12 upgrade - would be much appreciated!

matt-dalton commented 2 years ago

Btw @morrys do you think it would be easier to roll this out via a v4 patch or an official v5 release?

The v5 release candidate looked pretty solid when I was using it, but we could do some more testing with it if that would help.

morrys commented 2 years ago

Version 5 rc is stable I have not released the official version to check compatibility with relay v13 (released yesterday).

I'm back from vacation on Monday and I'll let you know

morrys commented 2 years ago

released with react-relay-offline 5.0.0 https://github.com/morrys/react-relay-offline/releases/tag/v5.0.0

I am now working on relay-hooks version 7 to support relay v13 and later release react-relay-offline version 6

Let me know if you have a problem with the new release 💯