Skip to content

Instantly share code, notes, and snippets.

@cpsubrian
Created March 7, 2017 16:56
Show Gist options
  • Save cpsubrian/51cbd9c497f9573f29d368eaa85cfa31 to your computer and use it in GitHub Desktop.
Save cpsubrian/51cbd9c497f9573f29d368eaa85cfa31 to your computer and use it in GitHub Desktop.
Chainable request helper for testing express apps with jest
import request from './request'
// Your express app (or router).
import app from './app'
it('can serve requests', () => {
return request
.app(app)
.post('/foo/bar')
.header({authorization: 'Bearer mytoken'})
.payload({user: 'Brian'})
.debug() // log the req and res
.tap((req, res) => {
// Stash something from the req or res
})
.expect('statusCode', 'toBe', 200)
.expect('payload.deep.property', 'toEqual', {some: 'object'})
// Start another request after the prev one.
.next()
.get('/foo/bar')
.expect('payload.message', 'toBe', 'This thing exists')
})
/* eslint-env jest */
import _ from 'lodash'
import util from 'util'
import {inject} from 'pickleback'
class Request {
constructor (options, prev) {
this.prev = prev || Promise.resolve()
this.expectations = []
this.taps = []
this.options = _.defaultsDeep(options || {}, {
headers: {
'content-type': 'application/json'
}
})
}
app (app) {
this._app = app
return this
}
header (key, value) {
this.options.headers[key] = value
return this
}
payload (payload) {
this.options.payload = payload
return this
}
get (url) {
this.options.url = url
this.options.method = 'get'
return this
}
post (url) {
this.options.url = url
this.options.method = 'post'
return this
}
put (url) {
this.options.url = url
this.options.method = 'put'
return this
}
patch (url) {
this.options.url = url
this.options.method = 'patch'
return this
}
delete (url) {
this.options.url = url
this.options.method = 'delete'
return this
}
expect (...args) {
this.expectations.push({
prop: args[0],
method: args[1],
args: args.slice(2)
})
return this
}
inject () {
return (new Promise((resolve, reject) => {
inject(this._app || app, this.options, (res) => {
if (
res.payload &&
res.payload.length &&
res.headers['content-type'] &&
res.headers['content-type'].match(/application\/json/)
) {
res.rawPayload = res.payload
try {
res.payload = JSON.parse(res.rawPayload)
} catch (e) {
return reject(e)
}
}
return resolve(res)
})
}))
}
next (beforeNext) {
let opts = _.pick(this.options, 'headers')
let next = new Request(opts, this)
next._app = this._app
next._beforeNext = beforeNext
return next
}
tap (fn) {
this.taps.push(fn)
return this
}
debug () {
return this.tap((req, res) => {
console.log('request options:\n',
util.inspect(req.options, {colors: true})
)
console.log('response:\n',
util.inspect(
_.pick(res, 'statusCode', 'headers', 'payload'),
{colors: true}
)
)
})
}
run () {
return this
.prev
.then((res) => this._beforeNext ? this._beforeNext(this, res) : res)
.then(() => this.inject())
.then((res) => {
this.taps.forEach((fn) => {
fn(this, res)
})
this.expectations.forEach(({prop, method, args}) => {
let context = expect(_.get(res, prop))
let check = _.get(context, method)
check.apply(context, args)
})
return res
})
}
then (cb) {
return this.run().then(cb)
}
}
export default new Proxy({}, {
get (target, key) {
let req = new Request()
return req[key].bind(req)
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment