-
Notifications
You must be signed in to change notification settings - Fork 27k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Testing] Export utilities to help test middleware and next.config.js…
… routing (#70731) This PR adds some testing utilities for testing `middleware` and `next.config.js`. The goal is to make it easy for anyone to add tests that assert the behavior of routing, such as how `next.config.js#redirects`, `next.config.js#rewrites`, and `middleware` interact, or what paths are matched by middleware. This is useful to catch routing issues before the code ever reaches production. This code is exported in a new `next/server/testing` export to keep it separate from the rest of the `next/server` code. This testing code is marked as `unstable_` since it is not reusing all of the same code that the actual routing in `next dev` and `next start` is using. It is an approximation to make unit testing easier. # Middleware A new `unstable_doesMiddlewareMatch` function was added to assert when middleware will be run for a path. ```js import { unstable_doesMiddlewareMatch } from 'next/server/testing' expect( unstable_doesMiddlewareMatch({ config, nextConfig, url: '/test', }) ).toEqual(false) ``` Helpers were also created to test for whether the response was a redirect or rewrite. # Next.config.js A new `unstable_getResponseFromNextConfig` function was added to run `headers`, `redirects`, and `rewrites` functions from `next.config.js`, calling them in the order that they would actually be executed in. ```js import { getRedirectUrl, unstable_getResponseFromNextConfig } from 'next/server/testing' const response = await unstable_getResponseFromNextConfig({ url: 'https://nextjs.org/test', nextConfig: { async redirects() { return [{ source: '/test', destination: '/test2', permanent: false }] }, }, }) expect(response.status).toEqual(307) expect(getRedirectUrl(response)).toEqual('https://nextjs.org/test2') ``` --------- Co-authored-by: Zack Tanner <[email protected]> Co-authored-by: JJ Kasper <[email protected]>
- Loading branch information
1 parent
39c4cf9
commit a44a0d9
Showing
13 changed files
with
849 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from '../dist/experimental/testing/server' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('../dist/experimental/testing/server') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# next/experimental/testing/server | ||
|
||
This directory contains helpers for unit testing Next.js server code, such as | ||
routing using `next.config.js` or `middleware`. These utilities can be used to | ||
verify the behavior of redirects, rewrites, adding headers, or middleware logic | ||
before code reaches to production. | ||
|
||
See https://nextjs.org/docs/app/building-your-application/routing/middleware | ||
and https://nextjs.org/docs/app/api-reference/next-config-js for more details. |
206 changes: 206 additions & 0 deletions
206
packages/next/src/experimental/testing/server/config-testing-utils.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
import { unstable_getResponseFromNextConfig } from './config-testing-utils' | ||
import { getRewrittenUrl, isRewrite } from './utils' | ||
|
||
describe('config-testing-utils', () => { | ||
it('returns 200 for paths that do not match', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: '/test', | ||
nextConfig: {}, | ||
}) | ||
expect(response.status).toEqual(200) | ||
}) | ||
|
||
describe('redirects', () => { | ||
it('handles redirect', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: 'https://nextjs.org/test', | ||
nextConfig: { | ||
async redirects() { | ||
return [ | ||
{ source: '/test', destination: '/test2', permanent: false }, | ||
] | ||
}, | ||
}, | ||
}) | ||
expect(response.status).toEqual(307) | ||
expect(response.headers.get('location')).toEqual( | ||
'https://nextjs.org/test2' | ||
) | ||
}) | ||
|
||
it('handles redirect with params', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: 'https://nextjs.org/test/foo', | ||
nextConfig: { | ||
async redirects() { | ||
return [ | ||
{ | ||
source: '/test/:slug', | ||
destination: '/test2/:slug', | ||
permanent: false, | ||
}, | ||
] | ||
}, | ||
}, | ||
}) | ||
expect(response.status).toEqual(307) | ||
expect(response.headers.get('location')).toEqual( | ||
'https://nextjs.org/test2/foo' | ||
) | ||
}) | ||
|
||
it('redirects take precedence over rewrites', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: 'https://nextjs.org/test/foo', | ||
nextConfig: { | ||
async redirects() { | ||
return [ | ||
{ | ||
source: '/test/:slug', | ||
destination: '/test2/:slug', | ||
permanent: false, | ||
}, | ||
] | ||
}, | ||
async rewrites() { | ||
return [ | ||
{ | ||
source: '/test/:path*', | ||
destination: 'https://example.com/:path*', | ||
}, | ||
] | ||
}, | ||
}, | ||
}) | ||
expect(response.status).toEqual(307) | ||
expect(response.headers.get('location')).toEqual( | ||
'https://nextjs.org/test2/foo' | ||
) | ||
}) | ||
}) | ||
|
||
describe('rewrites', () => { | ||
it('handles rewrite', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: 'https://nextjs.org/test/subpath', | ||
nextConfig: { | ||
async headers() { | ||
return [ | ||
{ | ||
source: '/test/:path+', | ||
headers: [ | ||
{ | ||
key: 'X-Custom-Header', | ||
value: 'custom-value', | ||
}, | ||
], | ||
}, | ||
] | ||
}, | ||
async rewrites() { | ||
return [ | ||
{ | ||
source: '/test/:path*', | ||
destination: 'https://example.com/:path*', | ||
}, | ||
] | ||
}, | ||
}, | ||
}) | ||
expect(isRewrite(response)).toEqual(true) | ||
expect(getRewrittenUrl(response)).toEqual('https://example.com/subpath') | ||
expect(response.headers.get('x-custom-header')).toEqual('custom-value') | ||
}) | ||
|
||
it('beforeFiles rewrites take precedence over afterFiles and fallback', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: 'https://nextjs.org/test/subpath', | ||
nextConfig: { | ||
async rewrites() { | ||
return { | ||
beforeFiles: [ | ||
{ | ||
source: '/test/:path*', | ||
destination: 'https://example.com/:path*', | ||
}, | ||
], | ||
afterFiles: [ | ||
{ | ||
source: '/test/:path*', | ||
destination: 'https://wrong-example.com/:path*', | ||
}, | ||
], | ||
fallback: [ | ||
{ | ||
source: '/test/:path*', | ||
destination: 'https://wrong-example.com/:path*', | ||
}, | ||
], | ||
} | ||
}, | ||
}, | ||
}) | ||
expect(isRewrite(response)).toEqual(true) | ||
expect(getRewrittenUrl(response)).toEqual('https://example.com/subpath') | ||
}) | ||
}) | ||
|
||
describe('headers', () => { | ||
it('simple match', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: 'https://nextjs.org/test/subpath', | ||
nextConfig: { | ||
async headers() { | ||
return [ | ||
{ | ||
source: '/test/:path+', | ||
headers: [ | ||
{ | ||
key: 'X-Custom-Header', | ||
value: 'custom-value', | ||
}, | ||
], | ||
}, | ||
] | ||
}, | ||
}, | ||
}) | ||
expect(response.headers.get('x-custom-header')).toEqual('custom-value') | ||
}) | ||
}) | ||
|
||
it('basePath', async () => { | ||
const response = await unstable_getResponseFromNextConfig({ | ||
url: 'https://nextjs.org/test', | ||
nextConfig: { | ||
basePath: '/base-path', | ||
async headers() { | ||
return [ | ||
{ | ||
// When basePath is defined, basePath is automatically added to these expressions. | ||
source: '/:path+', | ||
headers: [ | ||
{ | ||
key: 'X-Using-Base-Path', | ||
value: '1', | ||
}, | ||
], | ||
}, | ||
{ | ||
source: '/:path+', | ||
headers: [ | ||
{ | ||
key: 'X-Custom-Header', | ||
value: '1', | ||
}, | ||
], | ||
basePath: false, | ||
}, | ||
] | ||
}, | ||
}, | ||
}) | ||
expect(response.headers.get('x-custom-header')).toEqual('1') | ||
expect(response.headers.get('x-using-base-path')).toBeNull() | ||
}) | ||
}) |
Oops, something went wrong.