Skip to content

Commit

Permalink
feat: adds a onMissedRequest hook for seeing missed mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
James1x0 committed Jan 31, 2024
1 parent f1d7ada commit a778077
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 3 deletions.
8 changes: 8 additions & 0 deletions docs/api/MockAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ const agent = new Agent()
const mockAgent = new MockAgent({ agent })
```

### Example - MockAgent instantiation with missed request callback

```js
import { MockAgent } from 'undici'

const mockAgent = new MockAgent({ onMissedRequest: (error) => console.log(`A request wasn't handled: ${error.message}`) })
```

## Instance Methods

### `MockAgent.get(origin)`
Expand Down
8 changes: 7 additions & 1 deletion lib/mock/mock-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const {
kNetConnect,
kGetNetConnect,
kOptions,
kFactory
kFactory,
kOnMissedRequest
} = require('./mock-symbols')
const MockClient = require('./mock-client')
const MockPool = require('./mock-pool')
Expand All @@ -35,6 +36,11 @@ class MockAgent extends Dispatcher {
const agent = opts?.agent ? opts.agent : new Agent(opts)
this[kAgent] = agent

if (opts?.onMissedRequest && typeof opts.onMissedRequest !== 'function') {
throw new InvalidArgumentError('Argument opts.onMissedRequest must be a callback')
}

this[kOnMissedRequest] = opts?.onMissedRequest
this[kClients] = agent[kClients]
this[kOptions] = buildMockOptions(opts)
}
Expand Down
3 changes: 2 additions & 1 deletion lib/mock/mock-symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ module.exports = {
kIsMockActive: Symbol('is mock active'),
kNetConnect: Symbol('net connect'),
kGetNetConnect: Symbol('get net connect'),
kConnected: Symbol('connected')
kConnected: Symbol('connected'),
kOnMissedRequest: Symbol('onMissedRequest')
}
7 changes: 6 additions & 1 deletion lib/mock/mock-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const {
kMockAgent,
kOriginalDispatch,
kOrigin,
kGetNetConnect
kGetNetConnect,
kOnMissedRequest
} = require('./mock-symbols')
const { buildURL, nop } = require('../core/util')
const { STATUS_CODES } = require('node:http')
Expand Down Expand Up @@ -307,6 +308,10 @@ function buildMockDispatch () {
mockDispatch.call(this, opts, handler)
} catch (error) {
if (error instanceof MockNotMatchedError) {
if (agent[kOnMissedRequest]) {
agent[kOnMissedRequest](error)
}

const netConnect = agent[kGetNetConnect]()
if (netConnect === false) {
throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`)
Expand Down
39 changes: 39 additions & 0 deletions test/mock-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { test } = require('tap')
const { createServer } = require('node:http')
const { promisify } = require('node:util')
const { stub } = require('sinon')
const { request, setGlobalDispatcher, MockAgent, Agent } = require('..')
const { getResponse } = require('../lib/mock/mock-utils')
const { kClients, kConnected } = require('../lib/core/symbols')
Expand Down Expand Up @@ -385,6 +386,44 @@ test('MockAgent - basic intercept with request', async (t) => {
})
})

test('MockAgent - basic missed request', async (t) => {
t.plan(5)

const server = createServer((req, res) => {
res.setHeader('content-type', 'text/plain')
res.end('hello from the other side')
})
t.teardown(server.close.bind(server))

await promisify(server.listen.bind(server))(0)

const baseUrl = `http://localhost:${server.address().port}`
const onMissedRequest = stub()
const mockAgent = new MockAgent({ onMissedRequest })

setGlobalDispatcher(mockAgent)
t.teardown(mockAgent.close.bind(mockAgent))
const mockPool = mockAgent.get(baseUrl)

mockPool.intercept({
path: '/justatest?hello=there&see=ya',
method: 'GET'
}).reply(200, { wont: 'match' }, {
headers: { 'content-type': 'application/json' }
})

const { statusCode, headers, body } = await request(`${baseUrl}/justatest?hello=there&another=one`, {
method: 'GET'
})

t.equal(statusCode, 200, 'Status code is 200')
t.equal(headers['content-type'], 'text/plain', 'Content type is text/plain')
const responseBody = await getResponse(body)
t.equal(responseBody, 'hello from the other side', 'Body is from the local server')
t.equal(onMissedRequest.calledOnce, true, 'onMissedRequest calledOnce')
t.ok(onMissedRequest.firstCall.args[0].message.includes('Mock dispatch not matched for path \'/justatest?another=one&hello=there\''), 'Error message matches')
})

test('MockAgent - should support local agents', async (t) => {
t.plan(4)

Expand Down

0 comments on commit a778077

Please sign in to comment.