Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add testing/fixture docs (#554)

* Add testing docs

* Add testing docs

* Clarify what is react specific

* Update docs/core/testing.md

* Update docs/core/testing.md

* Move Testing to Advanced

authored by

Andy Richardson and committed by
GitHub
5cf197a5 8cc65a4d

+221 -1
+221 -1
docs/advanced/testing.md
··· 1 1 --- 2 2 title: Testing 3 - order: 3 3 + order: 4 4 4 --- 5 5 6 + 6 7 # Testing 8 + 9 + When testing your components, you're likely going to want to check arguments and force different states for your components using Urql. 10 + 11 + > **Note:** Examples demonstrate the _React hooks_ version of Urql being used but underlying patterns apply to all implementations. 12 + 13 + ## Mocking the client 14 + 15 + For the most part, Urql's hooks are just adapters for talking to the Urql client. 16 + 17 + The way in which they do this is by making calls to the client via context. 18 + 19 + - `useQuery` calls `executeQuery` 20 + - `useMutation` calls `executeMutation` 21 + - `useSubscription` calls `executeSubscription` 22 + 23 + Here's an example client mock being used while testing a component. 24 + 25 + ```tsx 26 + import { mount } from 'enzyme'; 27 + import { Provider } from 'urql'; 28 + import { MyComponent } from './MyComponent'; 29 + 30 + const mockClient = { 31 + executeQuery: jest.fn(), 32 + executeMutation: jest.fn(), 33 + executeSubscription: jest.fn(), 34 + }; 35 + 36 + it('renders', () => { 37 + const wrapper = mount( 38 + <Provider value={mockClient}> 39 + <MyComponent /> 40 + </Provider> 41 + ); 42 + }); 43 + ``` 44 + 45 + ## Testing calls to the client 46 + 47 + Once you have your mock setup, calls to the client can be tested. 48 + 49 + ```tsx 50 + it('skips the query', () => { 51 + mount( 52 + <Provider value={mockClient}> 53 + <MyComponent skip={true} /> 54 + </Provider> 55 + ); 56 + expect(mockClient.executeQuery).toBeCalledTimes(0); 57 + }); 58 + ``` 59 + 60 + Testing mutations and subscriptions also work in a similar fashion. 61 + 62 + ```tsx 63 + it('triggers a mutation', () => { 64 + const wrapper = mount( 65 + <Provider value={mockClient}> 66 + <MyComponent /> 67 + </Provider> 68 + ); 69 + const variables = { 70 + name: 'Carla', 71 + }; 72 + 73 + wrapper 74 + .find('input') 75 + .simulate('change', { currentTarget: { value: variables.name } }); 76 + wrapper.find('button').simulate('click'); 77 + 78 + expect(mockClient.executeMutation).toBeCalledTimes(1); 79 + expect(mockClient.executeMutation).toBeCalledWith( 80 + expect.objectContaining({ variables }) 81 + ); 82 + }); 83 + ``` 84 + 85 + ## Forcing states 86 + 87 + For testing render output, or creating fixtures, you may want to force the state of your components. 88 + 89 + ### Fetching 90 + 91 + Fetching states can be simulated by returning a stream which never returns. Wonka provides a utility for this, aptly called `never`. 92 + 93 + Here's a fixture which stays in the _fetching_ state. 94 + 95 + ```tsx 96 + import { Provider } from 'urql'; 97 + import { never } from 'wonka'; 98 + import { MyComponent } from './MyComponent'; 99 + 100 + const fetchingState = { 101 + executeQuery: () => never, 102 + }; 103 + 104 + export default ( 105 + <Provider value={fetchingState}> 106 + <MyComponent /> 107 + </Provider> 108 + ); 109 + ``` 110 + 111 + ### Response (success) 112 + 113 + 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. 114 + 115 + **Example snapshot test of response state** 116 + 117 + ```tsx 118 + const responseState = { 119 + executeQuery: () => 120 + fromValue({ 121 + data: { 122 + posts: [ 123 + { id: 1, title: 'Post title', content: 'This is a post' }, 124 + { id: 3, title: 'Final post', content: 'Final post here' }, 125 + ], 126 + }, 127 + }), 128 + }; 129 + 130 + it('matches snapshot', () => { 131 + const wrapper = mount( 132 + <Provider value={responseState}> 133 + <MyComponent /> 134 + </Provider> 135 + ); 136 + expect(wrapper).toMatchSnapshot(); 137 + }); 138 + ``` 139 + 140 + ### Response (error) 141 + 142 + Error responses are similar to success responses, only the value in the stream is changed. 143 + 144 + ```tsx 145 + import { Provider, CombinedError } from 'urql'; 146 + 147 + const errorState = { 148 + executeQuery: () => 149 + fromValue({ 150 + error: new CombinedError({ 151 + networkError: Error('something went wrong!'), 152 + }), 153 + }), 154 + }; 155 + ``` 156 + 157 + ### Handling multiple hooks 158 + 159 + Returning different values for many `useQuery` calls can be done by introducing conditionals into the mocked client functions. 160 + 161 + ```tsx 162 + const mockClient = () => { 163 + executeQuery: ({ query }) => { 164 + if (query === GET_USERS) { 165 + return fromValue(usersResponse); 166 + } 167 + 168 + if (query === GET_POSTS) { 169 + return fromValue(postsResponse); 170 + } 171 + }; 172 + }; 173 + ``` 174 + 175 + ## Simulating changes 176 + 177 + Simulating multiple responses can be useful, particularly testing `useEffect` calls dependent on changing query responses. 178 + 179 + 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. 180 + 181 + Below is an example of simulating subsequent responses (such as a cache update/refetch) in a test. 182 + 183 + ```tsx 184 + import { mount } from 'enzyme'; 185 + import { act } from 'react-dom/test-utils'; 186 + import { Provider } from 'urql'; 187 + import { makeSubject } from 'wonka'; 188 + import { MyComponent } from './MyComponent'; 189 + 190 + const [stream, pushResponse] = makeSubject(); 191 + 192 + const mockedClient = { 193 + executeQuery: () => stream, 194 + }; 195 + 196 + it('shows notification on updated data', () => { 197 + const wrapper = mount( 198 + <Provider value={mockedClient}> 199 + <MyComponent /> 200 + </Provider> 201 + ); 202 + 203 + // First response 204 + act(() => { 205 + pushResponse({ 206 + data: { 207 + posts: [{ id: 1, title: 'Post title', content: 'This is a post' }], 208 + }, 209 + }); 210 + }); 211 + expect(wrapper.find('dialog').exists()).toBe(false); 212 + 213 + // Second response 214 + act(() => { 215 + pushResponse({ 216 + data: { 217 + posts: [ 218 + { id: 1, title: 'Post title', content: 'This is a post' }, 219 + { id: 1, title: 'Post title', content: 'This is a post' }, 220 + ], 221 + }, 222 + }); 223 + }); 224 + expect(wrapper.find('dialog').exists()).toBe(true); 225 + }); 226 + ```