From 217a4be5cb3ddf2c1f46f501b602084ea089f82c Mon Sep 17 00:00:00 2001 From: Andy Richardson Date: Thu, 20 Feb 2020 16:41:25 +0000 Subject: [PATCH 1/6] Add testing docs --- docs/core/testing.md | 225 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 docs/core/testing.md diff --git a/docs/core/testing.md b/docs/core/testing.md new file mode 100644 index 0000000000..3d606df904 --- /dev/null +++ b/docs/core/testing.md @@ -0,0 +1,225 @@ +--- +title: Testing +order: 6 +--- + + + +# Testing + +When testing your components, you're likely going to want to check arguments and force different states from the Urql hooks, this is the place to get started. + +## Communicating with hooks + +Under the hood, Urql's hooks are just adapters for talking to the Urql client. + +The way in which they do this is by making calls to the client via context. + +- `useQuery` calls `executeQuery` +- `useMutation` calls `executeMutation` +- `useSubscription` calls `executeSubscription` + +Here's an example client mock being used while testing a component. + +```tsx +import { mount } from 'enzyme'; +import { Provider } from 'urql'; +import { MyComponent } from './MyComponent'; + +const mockClient = { + executeQuery: jest.fn(), + executeMutation: jest.fn(), + executeSubscription: jest.fn(), +}; + +it('renders', () => { + const wrapper = mount( + + + + ); +}); +``` + +## Testing calls to the client + +Once you have your mock setup, calls to the client can be tested. + +```tsx +it('skips the query', () => { + mount( + + + + ); + expect(mockClient.executeQuery).toBeCalledTimes(0); +}); +``` + +Testing mutations and subscriptions also work in a similar fashion. + +```tsx +it('triggers a mutation', () => { + const wrapper = mount( + + + + ); + const variables = { + name: 'Carla', + }; + + wrapper + .find('input') + .simulate('change', { currentTarget: { value: variables.name } }); + wrapper.find('button').simulate('click'); + + expect(mockClient.executeMutation).toBeCalledTimes(1); + expect(mockClient.executeMutation).toBeCalledWith( + expect.objectContaining({ variables }) + ); +}); +``` + +## Forcing states + +For testing render output, or creating fixtures, you may want to force the state of your components. + +### Fetching + +Fetching states can be simulated by returning a stream which never returns. Wonka provides a utility for this aptly called `never`. + +Here's a fixture which stays in the _fetching_ state. + +```tsx +import { Provider } from 'urql'; +import { never } from 'wonka'; +import { MyComponent } from './MyComponent'; + +const fetchingState = { + executeQuery: () => never, +}; + +export default ( + + + +); +``` + +### Response (success) + +Response states are simulated by providing a stream which contains a network response. For single responses, Wonka's `fromValue` function can do this for us. + +**Example snapshot test of response state** + +```tsx +const responseState = { + executeQuery: () => + fromValue({ + data: { + posts: [ + { id: 1, title: 'Post title', content: 'This is a post' }, + { id: 3, title: 'Final post', content: 'Final post here' }, + ], + }, + }), +}; + +it('matches snapshot', () => { + const wrapper = mount( + + + + ); + expect(wrapper).toMatchSnapshot(); +}); +``` + +### Response (error) + +Error responses are similar to success responses, only the value in the stream is changed. + +```tsx +import { Provider, CombinedError } from 'urql'; + +const errorState = { + executeQuery: () => + fromValue({ + error: new CombinedError({ + networkError: Error('something went wrong!'), + }), + }), +}; +``` + +### Handling multiple hooks + +Returning different values for many `useQuery` calls can be done by introducing conditionals into the mocked client functions. + +```tsx +const mockClient = () => { + executeQuery: ({ query }) => { + if (query === GET_USERS) { + return fromValue(usersResponse); + } + + if (query === GET_POSTS) { + return fromValue(postsResponse); + } + }; +}; +``` + +## Simulating changes + +Simulating multiple responses can be useful, particularly testing `useEffect` calls dependent on changing query responses. + +For this, a _subject_ is the way to go. In short, it's a stream which you can push responses to. The `makeSubject` function from Wonka is what you'll want to use for this purpose. + +Below is an example of simulating subsequent responses (such as a cache update/refetch) in a test. + +```tsx +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { Provider } from 'urql'; +import { makeSubject } from 'wonka'; +import { MyComponent } from './MyComponent'; + +const [stream, pushResponse] = makeSubject(); + +const mockedClient = { + executeQuery: () => stream, +}; + +it('shows notification on updated data', () => { + const wrapper = mount( + + + + ); + + // First response + act(() => { + pushResponse({ + data: { + posts: [{ id: 1, title: 'Post title', content: 'This is a post' }], + }, + }); + }); + expect(wrapper.find('dialog').exists()).toBe(false); + + // Second response + act(() => { + pushResponse({ + data: { + posts: [ + { id: 1, title: 'Post title', content: 'This is a post' }, + { id: 1, title: 'Post title', content: 'This is a post' }, + ], + }, + }); + }); + expect(wrapper.find('dialog').exists()).toBe(true); +}); +``` From a51f9d62835a24c1f97e725889c3fd34b82665ef Mon Sep 17 00:00:00 2001 From: Andy Richardson Date: Thu, 20 Feb 2020 16:42:38 +0000 Subject: [PATCH 2/6] Add testing docs --- docs/core/testing.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/core/testing.md b/docs/core/testing.md index 3d606df904..82433d4444 100644 --- a/docs/core/testing.md +++ b/docs/core/testing.md @@ -69,15 +69,11 @@ it('triggers a mutation', () => { name: 'Carla', }; - wrapper - .find('input') - .simulate('change', { currentTarget: { value: variables.name } }); + wrapper.find('input').simulate('change', { currentTarget: { value: variables.name } }); wrapper.find('button').simulate('click'); expect(mockClient.executeMutation).toBeCalledTimes(1); - expect(mockClient.executeMutation).toBeCalledWith( - expect.objectContaining({ variables }) - ); + expect(mockClient.executeMutation).toBeCalledWith(expect.objectContaining({ variables })); }); ``` From f8dbf9d38351dc561b6db05ac4c14497395854b0 Mon Sep 17 00:00:00 2001 From: Andy Richardson Date: Fri, 21 Feb 2020 12:31:52 +0000 Subject: [PATCH 3/6] Clarify what is react specific --- docs/core/testing.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/core/testing.md b/docs/core/testing.md index 82433d4444..5e80038ed8 100644 --- a/docs/core/testing.md +++ b/docs/core/testing.md @@ -7,11 +7,13 @@ order: 6 # Testing -When testing your components, you're likely going to want to check arguments and force different states from the Urql hooks, this is the place to get started. +When testing your components, you're likely going to want to check arguments and force different states for your components using Urql. -## Communicating with hooks +> **Note:** Examples demonstrate the _React hooks_ version of Urql being used but underlying patterns apply to all implementations. -Under the hood, Urql's hooks are just adapters for talking to the Urql client. +## Mocking the client + +For the most part, Urql's hooks are just adapters for talking to the Urql client. The way in which they do this is by making calls to the client via context. @@ -69,11 +71,15 @@ it('triggers a mutation', () => { name: 'Carla', }; - wrapper.find('input').simulate('change', { currentTarget: { value: variables.name } }); + wrapper + .find('input') + .simulate('change', { currentTarget: { value: variables.name } }); wrapper.find('button').simulate('click'); expect(mockClient.executeMutation).toBeCalledTimes(1); - expect(mockClient.executeMutation).toBeCalledWith(expect.objectContaining({ variables })); + expect(mockClient.executeMutation).toBeCalledWith( + expect.objectContaining({ variables }) + ); }); ``` From a9b1680206945d43976b1a5f6c60157cefa4ecb7 Mon Sep 17 00:00:00 2001 From: Andy Richardson Date: Fri, 21 Feb 2020 12:34:30 +0000 Subject: [PATCH 4/6] Update docs/core/testing.md --- docs/core/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/testing.md b/docs/core/testing.md index 5e80038ed8..4970c8e019 100644 --- a/docs/core/testing.md +++ b/docs/core/testing.md @@ -89,7 +89,7 @@ For testing render output, or creating fixtures, you may want to force the state ### Fetching -Fetching states can be simulated by returning a stream which never returns. Wonka provides a utility for this aptly called `never`. +Fetching states can be simulated by returning a stream which never returns. Wonka provides a utility for this, aptly called `never`. Here's a fixture which stays in the _fetching_ state. From f37d95b9aba811f986de6662624377e131ccade5 Mon Sep 17 00:00:00 2001 From: Andy Richardson Date: Fri, 21 Feb 2020 12:34:48 +0000 Subject: [PATCH 5/6] Update docs/core/testing.md --- docs/core/testing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/core/testing.md b/docs/core/testing.md index 4970c8e019..1b03312470 100644 --- a/docs/core/testing.md +++ b/docs/core/testing.md @@ -3,7 +3,6 @@ title: Testing order: 6 --- - # Testing From 54c93689858264e65cbad45938c3ff6d8b9dec71 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 25 Feb 2020 09:31:58 -0800 Subject: [PATCH 6/6] Move Testing to Advanced --- docs/advanced/testing.md | 222 +++++++++++++++++++++++++++++++++++++- docs/core/testing.md | 226 --------------------------------------- 2 files changed, 221 insertions(+), 227 deletions(-) delete mode 100644 docs/core/testing.md diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 82fd102f39..d00e958bd8 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -1,6 +1,226 @@ --- title: Testing -order: 3 +order: 4 --- + # Testing + +When testing your components, you're likely going to want to check arguments and force different states for your components using Urql. + +> **Note:** Examples demonstrate the _React hooks_ version of Urql being used but underlying patterns apply to all implementations. + +## Mocking the client + +For the most part, Urql's hooks are just adapters for talking to the Urql client. + +The way in which they do this is by making calls to the client via context. + +- `useQuery` calls `executeQuery` +- `useMutation` calls `executeMutation` +- `useSubscription` calls `executeSubscription` + +Here's an example client mock being used while testing a component. + +```tsx +import { mount } from 'enzyme'; +import { Provider } from 'urql'; +import { MyComponent } from './MyComponent'; + +const mockClient = { + executeQuery: jest.fn(), + executeMutation: jest.fn(), + executeSubscription: jest.fn(), +}; + +it('renders', () => { + const wrapper = mount( + + + + ); +}); +``` + +## Testing calls to the client + +Once you have your mock setup, calls to the client can be tested. + +```tsx +it('skips the query', () => { + mount( + + + + ); + expect(mockClient.executeQuery).toBeCalledTimes(0); +}); +``` + +Testing mutations and subscriptions also work in a similar fashion. + +```tsx +it('triggers a mutation', () => { + const wrapper = mount( + + + + ); + const variables = { + name: 'Carla', + }; + + wrapper + .find('input') + .simulate('change', { currentTarget: { value: variables.name } }); + wrapper.find('button').simulate('click'); + + expect(mockClient.executeMutation).toBeCalledTimes(1); + expect(mockClient.executeMutation).toBeCalledWith( + expect.objectContaining({ variables }) + ); +}); +``` + +## Forcing states + +For testing render output, or creating fixtures, you may want to force the state of your components. + +### Fetching + +Fetching states can be simulated by returning a stream which never returns. Wonka provides a utility for this, aptly called `never`. + +Here's a fixture which stays in the _fetching_ state. + +```tsx +import { Provider } from 'urql'; +import { never } from 'wonka'; +import { MyComponent } from './MyComponent'; + +const fetchingState = { + executeQuery: () => never, +}; + +export default ( + + + +); +``` + +### Response (success) + +Response states are simulated by providing a stream which contains a network response. For single responses, Wonka's `fromValue` function can do this for us. + +**Example snapshot test of response state** + +```tsx +const responseState = { + executeQuery: () => + fromValue({ + data: { + posts: [ + { id: 1, title: 'Post title', content: 'This is a post' }, + { id: 3, title: 'Final post', content: 'Final post here' }, + ], + }, + }), +}; + +it('matches snapshot', () => { + const wrapper = mount( + + + + ); + expect(wrapper).toMatchSnapshot(); +}); +``` + +### Response (error) + +Error responses are similar to success responses, only the value in the stream is changed. + +```tsx +import { Provider, CombinedError } from 'urql'; + +const errorState = { + executeQuery: () => + fromValue({ + error: new CombinedError({ + networkError: Error('something went wrong!'), + }), + }), +}; +``` + +### Handling multiple hooks + +Returning different values for many `useQuery` calls can be done by introducing conditionals into the mocked client functions. + +```tsx +const mockClient = () => { + executeQuery: ({ query }) => { + if (query === GET_USERS) { + return fromValue(usersResponse); + } + + if (query === GET_POSTS) { + return fromValue(postsResponse); + } + }; +}; +``` + +## Simulating changes + +Simulating multiple responses can be useful, particularly testing `useEffect` calls dependent on changing query responses. + +For this, a _subject_ is the way to go. In short, it's a stream which you can push responses to. The `makeSubject` function from Wonka is what you'll want to use for this purpose. + +Below is an example of simulating subsequent responses (such as a cache update/refetch) in a test. + +```tsx +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { Provider } from 'urql'; +import { makeSubject } from 'wonka'; +import { MyComponent } from './MyComponent'; + +const [stream, pushResponse] = makeSubject(); + +const mockedClient = { + executeQuery: () => stream, +}; + +it('shows notification on updated data', () => { + const wrapper = mount( + + + + ); + + // First response + act(() => { + pushResponse({ + data: { + posts: [{ id: 1, title: 'Post title', content: 'This is a post' }], + }, + }); + }); + expect(wrapper.find('dialog').exists()).toBe(false); + + // Second response + act(() => { + pushResponse({ + data: { + posts: [ + { id: 1, title: 'Post title', content: 'This is a post' }, + { id: 1, title: 'Post title', content: 'This is a post' }, + ], + }, + }); + }); + expect(wrapper.find('dialog').exists()).toBe(true); +}); +``` diff --git a/docs/core/testing.md b/docs/core/testing.md deleted file mode 100644 index 1b03312470..0000000000 --- a/docs/core/testing.md +++ /dev/null @@ -1,226 +0,0 @@ ---- -title: Testing -order: 6 ---- - - -# Testing - -When testing your components, you're likely going to want to check arguments and force different states for your components using Urql. - -> **Note:** Examples demonstrate the _React hooks_ version of Urql being used but underlying patterns apply to all implementations. - -## Mocking the client - -For the most part, Urql's hooks are just adapters for talking to the Urql client. - -The way in which they do this is by making calls to the client via context. - -- `useQuery` calls `executeQuery` -- `useMutation` calls `executeMutation` -- `useSubscription` calls `executeSubscription` - -Here's an example client mock being used while testing a component. - -```tsx -import { mount } from 'enzyme'; -import { Provider } from 'urql'; -import { MyComponent } from './MyComponent'; - -const mockClient = { - executeQuery: jest.fn(), - executeMutation: jest.fn(), - executeSubscription: jest.fn(), -}; - -it('renders', () => { - const wrapper = mount( - - - - ); -}); -``` - -## Testing calls to the client - -Once you have your mock setup, calls to the client can be tested. - -```tsx -it('skips the query', () => { - mount( - - - - ); - expect(mockClient.executeQuery).toBeCalledTimes(0); -}); -``` - -Testing mutations and subscriptions also work in a similar fashion. - -```tsx -it('triggers a mutation', () => { - const wrapper = mount( - - - - ); - const variables = { - name: 'Carla', - }; - - wrapper - .find('input') - .simulate('change', { currentTarget: { value: variables.name } }); - wrapper.find('button').simulate('click'); - - expect(mockClient.executeMutation).toBeCalledTimes(1); - expect(mockClient.executeMutation).toBeCalledWith( - expect.objectContaining({ variables }) - ); -}); -``` - -## Forcing states - -For testing render output, or creating fixtures, you may want to force the state of your components. - -### Fetching - -Fetching states can be simulated by returning a stream which never returns. Wonka provides a utility for this, aptly called `never`. - -Here's a fixture which stays in the _fetching_ state. - -```tsx -import { Provider } from 'urql'; -import { never } from 'wonka'; -import { MyComponent } from './MyComponent'; - -const fetchingState = { - executeQuery: () => never, -}; - -export default ( - - - -); -``` - -### Response (success) - -Response states are simulated by providing a stream which contains a network response. For single responses, Wonka's `fromValue` function can do this for us. - -**Example snapshot test of response state** - -```tsx -const responseState = { - executeQuery: () => - fromValue({ - data: { - posts: [ - { id: 1, title: 'Post title', content: 'This is a post' }, - { id: 3, title: 'Final post', content: 'Final post here' }, - ], - }, - }), -}; - -it('matches snapshot', () => { - const wrapper = mount( - - - - ); - expect(wrapper).toMatchSnapshot(); -}); -``` - -### Response (error) - -Error responses are similar to success responses, only the value in the stream is changed. - -```tsx -import { Provider, CombinedError } from 'urql'; - -const errorState = { - executeQuery: () => - fromValue({ - error: new CombinedError({ - networkError: Error('something went wrong!'), - }), - }), -}; -``` - -### Handling multiple hooks - -Returning different values for many `useQuery` calls can be done by introducing conditionals into the mocked client functions. - -```tsx -const mockClient = () => { - executeQuery: ({ query }) => { - if (query === GET_USERS) { - return fromValue(usersResponse); - } - - if (query === GET_POSTS) { - return fromValue(postsResponse); - } - }; -}; -``` - -## Simulating changes - -Simulating multiple responses can be useful, particularly testing `useEffect` calls dependent on changing query responses. - -For this, a _subject_ is the way to go. In short, it's a stream which you can push responses to. The `makeSubject` function from Wonka is what you'll want to use for this purpose. - -Below is an example of simulating subsequent responses (such as a cache update/refetch) in a test. - -```tsx -import { mount } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import { Provider } from 'urql'; -import { makeSubject } from 'wonka'; -import { MyComponent } from './MyComponent'; - -const [stream, pushResponse] = makeSubject(); - -const mockedClient = { - executeQuery: () => stream, -}; - -it('shows notification on updated data', () => { - const wrapper = mount( - - - - ); - - // First response - act(() => { - pushResponse({ - data: { - posts: [{ id: 1, title: 'Post title', content: 'This is a post' }], - }, - }); - }); - expect(wrapper.find('dialog').exists()).toBe(false); - - // Second response - act(() => { - pushResponse({ - data: { - posts: [ - { id: 1, title: 'Post title', content: 'This is a post' }, - { id: 1, title: 'Post title', content: 'This is a post' }, - ], - }, - }); - }); - expect(wrapper.find('dialog').exists()).toBe(true); -}); -```