Skip to content

Commit 42663a7

Browse files
committed
Add delay option to stub request
1 parent 82d6cc7 commit 42663a7

File tree

7 files changed

+1236
-62
lines changed

7 files changed

+1236
-62
lines changed

docs.md

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ handleActions({
5151
return set('currentUser', action.payload.data, state)
5252
})
5353
})
54-
55-
*
5654
```
5755

5856
Returns **[Function][32]** An action handler that runs when a request is successful
@@ -74,8 +72,6 @@ handleActions({
7472
return set('userFetchError', action.payload.data, state)
7573
})
7674
})
77-
78-
*
7975
```
8076

8177
Returns **[Function][32]** An action handler that runs when a request is unsuccessful
@@ -105,8 +101,6 @@ handleActions({
105101
},
106102
)
107103
})
108-
109-
*
110104
```
111105

112106
Returns **[Function][32]** An action handler runs the handler that corresponds to the request status
@@ -127,8 +121,6 @@ handleActions({
127121
// This will do the same thing as the example for handleSuccess
128122
[apiActions.fetchUser]: setOnSuccess('currentSuccess')
129123
})
130-
131-
*
132124
```
133125

134126
Returns **[Function][32]** An action handler that runs when a request is unsuccessful
@@ -149,8 +141,6 @@ handleActions({
149141
// This will do the same thing as the example for handleFailure
150142
[apiActions.fetchUser]: setOnFailure('userFetchError')
151143
})
152-
153-
*
154144
```
155145

156146
Returns **[Function][32]** An action handler that runs when a request is successful
@@ -173,8 +163,6 @@ handleActions({
173163
// This will do the same thing as the example for handleResponse
174164
[apiActions.fetchUser]: setOnResponse('currentUser', 'userFetchError')
175165
})
176-
177-
*
178166
```
179167

180168
Returns **[Function][32]** An action handler
@@ -232,8 +220,6 @@ fetchUsers(5)
232220
handleActions({
233221
[apiActions.fetchUser]: (state, action) => ...
234222
})
235-
236-
*
237223
```
238224

239225
Returns **[Function][32]** An action creator that passes its arguments to `definition` and makes the resulting API request.
@@ -294,8 +280,6 @@ const REQ_FETCH_USERS = 'REQ_FETCH_USERS'
294280
dispatch(requestWithKey(REQ_FETCH_USERS, { url: '/users' }))
295281

296282
selectStatus(REQ_FETCH_USERS, state) // -> 'loading'
297-
298-
*
299283
```
300284

301285
## selectors
@@ -339,8 +323,6 @@ const fetchUsers = createRequest('FETCH_USERS', { url: '/users' })
339323
dispatch(fetchUsers())
340324

341325
apiSelectors.status(state, fetchUsers) // -> 'loading'
342-
343-
*
344326
```
345327

346328
## createStubRequest
@@ -356,6 +338,8 @@ If an exception is thrown from the data creator function, the "request" will rej
356338

357339
- `type` **[String][33]** A unique key that will be used to identify the request internally in redux
358340
- `dataDefinition` **([Object][36] \| [Function][32])** Data that the request will resolve with, or a function that returns data to resolve with.
341+
- `options` **[Object][36]?** Options object
342+
- `options.delay` **[Number][39]** Time (in ms) to delay the API request. Particularly useful when attempting to simulate loading states. (optional, default `0`)
359343

360344
### Examples
361345

@@ -382,7 +366,12 @@ export const fetchUser = createStubRequest('FETCH_USER', (id) => {
382366
fetchUsers(5)
383367
// -> won't make any api request, but will reject with the given error.
384368

385-
*
369+
// ** Simulating a response delay: **
370+
371+
export const fetchUser = createStubRequest('FETCH_USER', (id) => {
372+
return {
373+
id
374+
}}, { delay: 500 })
386375
```
387376

388377
Returns **[Function][32]** An action creator that passes its arguments to `dataDefinition` and makes the resulting stubbed API request.
@@ -462,3 +451,5 @@ Returns **[Function][32]** An action creator that passes its arguments to `dataD
462451
[37]: requestWithKey
463452

464453
[38]: selectStatus
454+
455+
[39]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

src/create-stub-request.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { isObject, isFunction, identity } from 'lodash'
1313
* @name createStubRequest
1414
* @param {String} type - A unique key that will be used to identify the request internally in redux
1515
* @param {Object|Function} dataDefinition - Data that the request will resolve with, or a function that returns data to resolve with.
16+
* @param {Object} [options] - Options object
17+
* @param {Number} [options.delay=0] - Time (in ms) to delay the API request. Particularly useful when attempting to simulate loading states.
1618
* @returns {Function} An action creator that passes its arguments to `dataDefinition` and makes the resulting stubbed API request.
1719
* @example
1820
*
@@ -38,6 +40,12 @@ import { isObject, isFunction, identity } from 'lodash'
3840
* fetchUsers(5)
3941
* // -> won't make any api request, but will reject with the given error.
4042
*
43+
* // ** Simulating a response delay: **
44+
*
45+
* export const fetchUser = createStubRequest('FETCH_USER', (id) => {
46+
* return {
47+
* id
48+
* }}, { delay: 500 })
4149
*/
4250

4351
function getStubData (definition, args) {
@@ -50,7 +58,7 @@ function getStubData (definition, args) {
5058
}
5159
}
5260

53-
function createStubRequest (type, definition=identity) {
61+
function createStubRequest (type, definition=identity, { delay }={}) {
5462
if (!type) throw new Error('Must include a type for your request.')
5563
if (!(isObject(definition) || isFunction(definition))) throw new Error('Request definition must be an object or a function.')
5664
function actionCreator (...args) {
@@ -61,6 +69,7 @@ function createStubRequest (type, definition=identity) {
6169
isStubError: isError,
6270
stubData,
6371
type,
72+
delay,
6473
}
6574
}
6675
}

src/middleware/middleware.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ import { isFunction } from 'lodash'
3333
*/
3434

3535
// custom HTTP method for stub requests- makes no call, but resolves/rejects with provided data
36-
function createStubRequest (data, isError) {
36+
function createStubRequest (data, isError, delay) {
3737
return function request () {
3838
return new Promise((resolve, reject) => {
39-
return isError ? reject(data) : resolve(data)
39+
return setTimeout(() => {
40+
return isError ? reject(data) : resolve(data)
41+
}, delay)
4042
})
4143
}
4244
}
@@ -71,6 +73,7 @@ function middleware (mainAdapter, options={}) {
7173
isStub,
7274
isStubError,
7375
stubData,
76+
delay=0,
7477
adapter=mainAdapter,
7578
} = mergedConfigOptions
7679
// Send user-specified request action
@@ -83,7 +86,7 @@ function middleware (mainAdapter, options={}) {
8386
// Send built-in request action
8487
next(actions.setStatusLoading(type))
8588
// Make the request
86-
const request = isStub ? createStubRequest(stubData, isStubError) : adapter
89+
const request = isStub ? createStubRequest(stubData, isStubError, delay) : adapter
8790
return request(mergedRequestOptions)
8891
.then(
8992
// Success handler

src/middleware/parse-options.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ function parseOptions ({
1313
isStub,
1414
isStubError,
1515
stubData,
16+
delay,
1617
adapter,
1718
...requestOptions
1819
}) {
@@ -28,10 +29,11 @@ function parseOptions ({
2829
isStub,
2930
isStubError,
3031
stubData,
32+
delay,
3133
adapter,
3234
}),
3335
requestOptions,
3436
}
3537
}
3638

37-
export default parseOptions
39+
export default parseOptions

test/create-stub-request.test.js

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,49 @@
11
import { createStubRequest, LP_API } from '../src'
22
import { REQUEST_TYPE } from './fixtures'
33

4-
test('createStubRequest requires a type argument', () => {
5-
expect(createStubRequest).toThrow()
6-
})
7-
8-
test('createStubRequest accepts object data', () => {
9-
const stubData = { foo: 'bar' }
10-
const actionCreator = createStubRequest(REQUEST_TYPE, stubData)
11-
const action = actionCreator()
12-
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, stubData })
13-
})
14-
15-
test('createStubRequest accepts function data creator', () => {
16-
const actionCreator = createStubRequest(REQUEST_TYPE, (arg) => ({
17-
foo: arg
18-
}))
19-
const action = actionCreator('bar')
20-
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, stubData: { foo: 'bar' } })
21-
})
22-
23-
test('createStubRequest function sets data and error flag from thrown exception', () => {
24-
const myException = new Error('oops')
25-
const actionCreator = createStubRequest(REQUEST_TYPE, () => {
26-
throw myException
4+
describe('createStubRequest', () => {
5+
test('requires a type argument', () => {
6+
expect(createStubRequest).toThrow()
7+
})
8+
9+
test('accepts object data', () => {
10+
const stubData = { foo: 'bar' }
11+
const actionCreator = createStubRequest(REQUEST_TYPE, stubData)
12+
const action = actionCreator()
13+
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, stubData })
14+
})
15+
16+
test('accepts function data creator', () => {
17+
const actionCreator = createStubRequest(REQUEST_TYPE, (arg) => ({
18+
foo: arg
19+
}))
20+
const action = actionCreator('bar')
21+
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, stubData: { foo: 'bar' } })
22+
})
23+
24+
test('accepts delay option', () => {
25+
const stubData = { foo: 'bar' }
26+
const actionCreator = createStubRequest(REQUEST_TYPE, stubData, { delay: 500 })
27+
const action = actionCreator()
28+
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub:true, stubData, delay: 500 })
29+
})
30+
31+
test('function sets data and error flag from thrown exception', () => {
32+
const myException = new Error('oops')
33+
const actionCreator = createStubRequest(REQUEST_TYPE, () => {
34+
throw myException
35+
})
36+
const action = actionCreator('bar')
37+
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, isStubError: true, stubData: myException })
38+
})
39+
40+
test('defaults to identity for data creator', () => {
41+
const actionCreator = createStubRequest(REQUEST_TYPE)
42+
const action = actionCreator('bar')
43+
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, stubData: 'bar' })
44+
})
45+
46+
test('rejects other types of data definitions', () => {
47+
expect(() => createStubRequest(REQUEST_TYPE, 'wtf')).toThrow()
2748
})
28-
const action = actionCreator('bar')
29-
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, isStubError: true, stubData: myException })
30-
})
31-
32-
test('createStubRequest defaults to identity for data creator', () => {
33-
const actionCreator = createStubRequest(REQUEST_TYPE)
34-
const action = actionCreator('bar')
35-
expect(action[LP_API]).toEqual({ type: REQUEST_TYPE, isStub: true, stubData: 'bar' })
36-
})
37-
38-
test('createStubRequest rejects other types of data definitions', () => {
39-
expect(() => createStubRequest(REQUEST_TYPE, 'wtf')).toThrow()
4049
})

test/middleware.test.js

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ test('middleware dispatches custom unauthorized action on auth error', () => {
136136
})
137137

138138
test('middleware resolves stubbed requests with provided data', () => {
139+
jest.useFakeTimers()
139140
const store = mockStore({})
140141
const stubData = { foo: 'bar' }
141142
const stubAction = {
@@ -144,12 +145,16 @@ test('middleware resolves stubbed requests with provided data', () => {
144145
stubData
145146
}
146147
}
147-
return store.dispatch(stubAction).then((res) => {
148+
const pendingPromise = store.dispatch(stubAction).then((res) => {
148149
expect(res).toEqual(stubData)
149150
})
151+
152+
jest.runAllTimers()
153+
return pendingPromise
150154
})
151155

152156
test('middleware rejects stubbed requests with error flag', () => {
157+
jest.useFakeTimers()
153158
expect.assertions(1)
154159
const stubData = { foo: 'bar' }
155160
const store = mockStore({})
@@ -160,9 +165,59 @@ test('middleware rejects stubbed requests with error flag', () => {
160165
stubData
161166
}
162167
}
163-
return store.dispatch(stubAction).catch((res) => {
168+
const pendingPromise = store.dispatch(stubAction).catch((res) => {
164169
expect(res).toEqual(stubData)
165170
})
171+
172+
jest.runAllTimers()
173+
return pendingPromise
174+
})
175+
176+
test('middleware responds after delay with provided data', () => {
177+
jest.useFakeTimers()
178+
const store = mockStore({})
179+
const stubData = { foo: 'bar' }
180+
const stubAction = {
181+
[LP_API]: {
182+
isStub: true,
183+
stubData,
184+
delay: 500,
185+
}
186+
}
187+
188+
const pendingPromise = store.dispatch(stubAction).then((res) => {
189+
expect(res).toEqual(stubData)
190+
expect(setTimeout).toHaveBeenCalledTimes(1)
191+
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 500)
192+
})
193+
194+
// Activate the timer
195+
jest.runAllTimers()
196+
return pendingPromise
197+
})
198+
199+
test('middleware rejects stubbed request after delay with error flag', () => {
200+
jest.useFakeTimers()
201+
const store = mockStore({})
202+
const stubData = { foo: 'bar' }
203+
const stubAction = {
204+
[LP_API]: {
205+
isStub: true,
206+
isStubError: true,
207+
stubData,
208+
delay: 500,
209+
}
210+
}
211+
212+
const pendingPromise = store.dispatch(stubAction).catch((res) => {
213+
expect(res).toEqual(stubData)
214+
expect(setTimeout).toHaveBeenCalledTimes(1)
215+
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 500)
216+
})
217+
218+
// Activate the timer
219+
jest.runAllTimers()
220+
return pendingPromise
166221
})
167222

168223
test('middleware dispatches default success action with correct data argument', () => {
@@ -182,4 +237,4 @@ test('middleware dispatches default failure action with correct data argument',
182237
// Internal FAILURE action
183238
expect(dispatchedActions[3].payload.data).toEqual({ status: 422, data: { url: failureUrl }})
184239
})
185-
})
240+
})

0 commit comments

Comments
 (0)