Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for request params in mock client #1466

Merged
merged 5 commits into from May 26, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api/MockPool.md
Expand Up @@ -57,6 +57,7 @@ Returns: `MockInterceptor` corresponding to the input options.
* **method** `string | RegExp | (method: string) => boolean` - a matcher for the HTTP request method.
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
* **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params.

### Return: `MockInterceptor`

Expand Down
11 changes: 8 additions & 3 deletions lib/mock/mock-interceptor.js
Expand Up @@ -10,6 +10,7 @@ const {
kMockDispatch
} = require('./mock-symbols')
const { InvalidArgumentError } = require('../core/errors')
const { buildURL } = require('../core/util')

/**
* Defines the scope API for an interceptor reply
Expand Down Expand Up @@ -70,9 +71,13 @@ class MockInterceptor {
// As per RFC 3986, clients are not supposed to send URI
// fragments to servers when they retrieve a document,
if (typeof opts.path === 'string') {
// Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
const parsedURL = new URL(opts.path, 'data://')
opts.path = parsedURL.pathname + parsedURL.search
if (opts.query) {
opts.path = buildURL(opts.path, opts.query)
} else {
// Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
const parsedURL = new URL(opts.path, 'data://')
opts.path = parsedURL.pathname + parsedURL.search
}
}
if (typeof opts.method === 'string') {
opts.method = opts.method.toUpperCase()
Expand Down
12 changes: 8 additions & 4 deletions lib/mock/mock-utils.js
Expand Up @@ -8,6 +8,7 @@ const {
kOrigin,
kGetNetConnect
} = require('./mock-symbols')
const { buildURL } = require('../core/util')

function matchValue (match, value) {
if (typeof match === 'string') {
Expand Down Expand Up @@ -98,10 +99,12 @@ function getResponseData (data) {
}

function getMockDispatch (mockDispatches, key) {
const resolvedPath = key.query ? buildURL(key.path, key.query) : key.path

// Match path
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, key.path))
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, resolvedPath))
if (matchedMockDispatches.length === 0) {
throw new MockNotMatchedError(`Mock dispatch not matched for path '${key.path}'`)
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`)
}

// Match method
Expand Down Expand Up @@ -146,12 +149,13 @@ function deleteMockDispatch (mockDispatches, key) {
}

function buildKey (opts) {
const { path, method, body, headers } = opts
const { path, method, body, headers, query } = opts
return {
path,
method,
body,
headers
headers,
query
}
}

Expand Down
81 changes: 81 additions & 0 deletions test/mock-client.js
Expand Up @@ -235,6 +235,87 @@ test('MockClient - should be able to set as globalDispatcher', async (t) => {
t.same(response, 'hello')
})

test('MockClient - should support query params', async (t) => {
t.plan(3)

const server = createServer((req, res) => {
res.setHeader('content-type', 'text/plain')
res.end('should not be called')
t.fail('should not be called')
t.end()
})
t.teardown(server.close.bind(server))

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

const baseUrl = `http://localhost:${server.address().port}`

const mockAgent = new MockAgent({ connections: 1 })
t.teardown(mockAgent.close.bind(mockAgent))

const mockClient = mockAgent.get(baseUrl)
t.type(mockClient, MockClient)
setGlobalDispatcher(mockClient)

const query = {
pageNum: 1
}
mockClient.intercept({
path: '/foo',
query,
method: 'GET'
}).reply(200, 'hello')

const { statusCode, body } = await request(`${baseUrl}/foo`, {
method: 'GET',
query
})
t.equal(statusCode, 200)

const response = await getResponse(body)
t.same(response, 'hello')
})

test('MockClient - should intercept query params with hardcoded path', async (t) => {
t.plan(3)

const server = createServer((req, res) => {
res.setHeader('content-type', 'text/plain')
res.end('should not be called')
t.fail('should not be called')
t.end()
})
t.teardown(server.close.bind(server))

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

const baseUrl = `http://localhost:${server.address().port}`

const mockAgent = new MockAgent({ connections: 1 })
t.teardown(mockAgent.close.bind(mockAgent))

const mockClient = mockAgent.get(baseUrl)
t.type(mockClient, MockClient)
setGlobalDispatcher(mockClient)

const query = {
pageNum: 1
}
mockClient.intercept({
path: '/foo?pageNum=1',
method: 'GET'
}).reply(200, 'hello')

const { statusCode, body } = await request(`${baseUrl}/foo`, {
method: 'GET',
query
})
t.equal(statusCode, 200)

const response = await getResponse(body)
t.same(response, 'hello')
})

test('MockClient - should be able to use as a local dispatcher', async (t) => {
t.plan(3)

Expand Down
1 change: 1 addition & 0 deletions test/mock-interceptor-unused-assertions.js
Expand Up @@ -204,6 +204,7 @@ test('returns unused interceptors', t => {
path: '/',
method: 'GET',
body: undefined,
query: undefined,
headers: undefined,
data: {
error: null,
Expand Down
2 changes: 2 additions & 0 deletions types/mock-interceptor.d.ts
Expand Up @@ -50,6 +50,8 @@ declare namespace MockInterceptor {
body?: string | RegExp | ((body: string) => boolean);
/** Headers to intercept on. */
headers?: Record<string, string | RegExp | ((body: string) => boolean)> | ((headers: Record<string, string>) => boolean);
/** Query params to intercept on */
query?: Record<string, any>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a test for this type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

}
export interface MockDispatch<TData extends object = object, TError extends Error = Error> extends Options {
times: number | null;
Expand Down