Repository: FormidableLabs/urql (ts)

Issue:
Testing a mutation
I have a mutation like so.
```javascript
const [addItemResult, addItem] = useMutation(addItemMutation)

//....

const addItemToCart = async () => {
 const {error} = await addItem(variables)
} 
```
So I trigger this mutation when the user submit the form. So pretty basic here.

Next I tried  to test the mutation by mocking the the `executeMutation` function. In the documentation it suggest to do something like this `executeMutation: jest.fn()`.  
However this was causing an error: `TypeError: Cannot read property 'length' of undefined` triggered by the function `Object.toPromise` from `wonka`

Dependencies
```json
{
"urql": "^1.9.6"
"wonka":  "^4.0.9",
"jest": "^25.3.0",
"@testing-library/react": "^10.0.2",
}
```
**Steps to reproduce**
Reproduce the example of the documentation. I used `react-testing-library` instead of `enzyme`
```javascript
const mockClient = {
 executeMutation: jest.fn()
}

it('triggers a mutation', () => {
  const { getByText,  getByTestId } = render(
    <Provider value={mockClient}>
      <MyComponent />
    </Provider>
  );
  const variables = {
    name: 'Carla',
  };
  fireEvent.change(getByTestId('input'), { target: { value: variables.name } })
  fireEvent.click(getByTestId(button'))
  expect(mockClient.executeMutation).toBeCalledTimes(1);
  expect(client.executeMutation.mock.calls[0][0]['variables']).toMatchObject(variables)
});
```

**Solution**
I came over this problem by doing this: ```executeMutation: jest.fn(() => empty)``` So my mock function returns an empty stream which is completed immediately. So I don't know if the best solution and if the documentation needs to be updated or not.

Pull request description:
Enhance testing page
Resolves #691 

## Summary

Update the testing page of the documentation :

- Add more details about the utilities you can use from `wonka`, to create the mock client.
- Add a subscription section
- Update the Simulating Changes code section. 

## Notes
I am new with this library so I am not sure about the best strategy to test subscription. I was able to test the code from the doc.




Patch:
diff --git a/.changeset/thirty-boxes-divide.md b/.changeset/thirty-boxes-divide.md
new file mode 100644
index 00000000..18429b35
--- /dev/null
+++ b/.changeset/thirty-boxes-divide.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-graphcache': patch
+---
+
+Change invalidation to check for undefined links since null is a valid value in graphql
diff --git a/exchanges/graphcache/src/operations/invalidate.ts b/exchanges/graphcache/src/operations/invalidate.ts
index b88d9c43..2e6ccfe3 100644
--- a/exchanges/graphcache/src/operations/invalidate.ts
+++ b/exchanges/graphcache/src/operations/invalidate.ts
@@ -18,7 +18,7 @@ export const invalidateEntity = (
 
   for (let i = 0, l = fields.length; i < l; i++) {
     const { fieldKey } = fields[i];
-    if (InMemoryData.readLink(entityKey, fieldKey)) {
+    if (InMemoryData.readLink(entityKey, fieldKey) !== undefined) {
       InMemoryData.writeLink(entityKey, fieldKey, undefined);
     } else {
       InMemoryData.writeRecord(entityKey, fieldKey, undefined);


Test patch:
diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md
index 5e9b22e9..78164a3c 100644
--- a/docs/advanced/testing.md
+++ b/docs/advanced/testing.md
@@ -21,17 +21,28 @@ The way in which they do this is by making calls to the client via context.
 - `useMutation` calls `executeMutation`
 - `useSubscription` calls `executeSubscription`
 
+In the section [Stream Patterns](../concepts/stream-patterns.md) we've seen, that all methods on the client operate with and return streams. These streams are created using the `wonka` library and we're able to create streams ourselves to mock the different states of our operations, e.g. fetching, errors, or success with data.
+
+You'll probably use one of these utility functions to create streams:
+
+- `never`: This stream doesn’t emit any values and never completes, which puts our `urql` code in a permanent `fetching: true` state.
+- `fromValue`: This utility function accepts a value and emits it immediately, which we can use to mock a result from the server.
+- `makeSubject`: Allows us to create a source and imperatively push responses, which is useful to test subscription and simulate changes, i.e. multiple states.
+
+Creating a mock `Client` is pretty quick as we'll create an object that contains the `Client`'s methods that the React `urql` hooks use. We'll mock the appropriate `execute` functions that we need to mock a set of hooks. After we've created the mock `Client` we can wrap components with the `Provider` from `urql` and pass it.
+
 Here's an example client mock being used while testing a component.
 
 ```tsx
 import { mount } from 'enzyme';
 import { Provider } from 'urql';
+import { never } from 'wonka';
 import { MyComponent } from './MyComponent';
 
 const mockClient = {
-  executeQuery: jest.fn(),
-  executeMutation: jest.fn(),
-  executeSubscription: jest.fn(),
+  executeQuery: jest.fn(() => never),
+  executeMutation: jest.fn(() => never),
+  executeSubscription: jest.fn(() => never),
 };
 
 it('renders', () => {
@@ -169,6 +180,51 @@ const mockClient = () => {
 };
 ```
 
+The above client we've created mocks all three operations — queries, mutations, and subscriptions — to always remain in the `fetching: true` state.
+
+## Subscriptions
+
+Testing subscriptions can be done by simulating the arrival of new data over time. To do this we may use the `interval` utility from `wonka`, which emits values on a timer, and for each value we can map over the response that we'd like to mock.
+
+If you prefer to have more control on when the new data is arriving you can use the `makeSubject` utility from `wonka`. You can see more details in the next section.
+
+Here's an example of testing a list component which uses a subscription.
+
+```tsx
+const mockClient = {
+  executeSubscription: jest.fn(query =>
+    pipe(
+      interval(200),
+      map((i: number) => ({
+        // To mock a full result, we need to pass a mock operation back as well
+        operation: {
+          operationName: 'subscription,
+          context: {},
+          ...query,
+        },
+        data: { posts: { id: i, title: 'Post title', content: 'This is a post' } },
+      }))
+    )
+  ),
+};
+
+it('should update the list', done => {
+  let index = 0;
+
+  const wrapper = mount(
+    <Provider value={mockClient}>
+      <MyComponent />
+    </Provider>
+  );
+
+  setTimeout(() => {
+    expect(wrapper.find('.list').children()).toHaveLength(index + 1); // See how many items are in the list
+    index++;
+    if (index === 2) done();
+  }, 200);
+});
+```
+
 ## Simulating changes
 
 Simulating multiple responses can be useful, particularly testing `useEffect` calls dependent on changing query responses.
@@ -184,10 +240,10 @@ import { Provider } from 'urql';
 import { makeSubject } from 'wonka';
 import { MyComponent } from './MyComponent';
 
-const [stream, pushResponse] = makeSubject();
+const { source: stream, next: pushResponse } = makeSubject();
 
 const mockedClient = {
-  executeQuery: () => stream,
+  executeQuery: jest.fn(() => stream),
 };
 
 it('shows notification on updated data', () => {
diff --git a/exchanges/graphcache/src/store/store.test.ts b/exchanges/graphcache/src/store/store.test.ts
index 0810beb1..f95ada5c 100644
--- a/exchanges/graphcache/src/store/store.test.ts
+++ b/exchanges/graphcache/src/store/store.test.ts
@@ -182,6 +182,41 @@ describe('Store with OptimisticMutationConfig', () => {
     expect(InMemoryData.readRecord('Todo:0', 'text')).toBe(undefined);
   });
 
+  it('should invalidate null keys correctly', () => {
+    const connection = gql`
+      query test {
+        exercisesConnection(page: { after: null, first: 10 }) {
+          id
+        }
+      }
+    `;
+
+    write(
+      store,
+      {
+        query: connection,
+      },
+      // @ts-ignore
+      {
+        exercisesConnection: null,
+      }
+    );
+    let { data } = query(store, { query: connection });
+
+    InMemoryData.initDataState(store.data, null);
+    expect((data as any).exercisesConnection).toEqual(null);
+    const fields = store.inspectFields({ __typename: 'Query' });
+    fields.forEach(({ fieldName, arguments: args }) => {
+      if (fieldName === 'exercisesConnection') {
+        store.invalidate('Query', fieldName, args);
+      }
+    });
+    InMemoryData.clearDataState();
+
+    ({ data } = query(store, { query: connection }));
+    expect(data).toBe(null);
+  });
+
   it('should be able to invalidate data with arguments', () => {
     write(
       store,