vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.38k stars 26.78k forks source link

Testing middleware with Jest: TypeError: this._headers.getAll is not a function #42374

Closed firatgh closed 1 year ago

firatgh commented 1 year ago

Verify canary release

Provide environment information

Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 21.6.0: Mon Aug 22 20:20:05 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T8101

Binaries: Node: 18.9.0 npm: 8.19.1 Yarn: 1.22.19 pnpm: N/A

Relevant packages: next: 13.0.2-canary.0 eslint-config-next: N/A react: 18.2.0 react-dom: 18.2.0

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

Hello everybody,

I am currently having issues trying to test the middleware.ts in my project, and to reproduce it, I've created a small project from next.js/examples/with-jest template, and here is the repo.

Screenshot 2022-11-02 at 14 51 50

The middleware:

export function middleware(req: NextRequest) {
    console.log('Hello')
    return NextResponse.next({headers: new Headers()})
}

The test:

describe('Middleware', () => {
  it('middleware', async () => {
    const res = await middleware(new NextRequest('http://localhost/'))

    console.log('res', res)
  })
})

Other findings:

I've also dug a little bit, and it seems that node_modules/next/server/web/spec-extension/cookies/response-cookies.ts:36:35 is expected to fail, I just don't know in which circumstances:

Screenshot_02_11_2022__14_55

Other things I've tried:

Changing jestEnvironment to node didn't yield the expected result. I've done it by adding the following comment in the test:

/**
 * @jest-environment node
 */

Thanks in advance

Expected Behavior

I expect to be able to test the middleware.

Link to reproduction

https://github.com/firatgh/nextjs-with-jest

To Reproduce

yarn test

michalstrzelecki commented 1 year ago

Yes, I am unable to test my middleware now.

hrasoa commented 1 year ago

I have the same issue, it's related to https://github.com/vercel/next.js/pull/41526 in packages/next/server/web/spec-extension/cookies/response-cookies.ts

const headers = this._headers.getAll('set-cookie')

How should we now instantiate a new NextResponse ?

firatgh commented 1 year ago

@michalstrzelecki @hrasoa I ended up mocking the .getAll() method before any module I was testing imported NextResponse

Headers.prototype.getAll = () => []

// or if your are using TS

(Headers.prototype as any).getAll = () => []

It's not ideal, but I was kind of forced to do that.

Cheers.

AndrewVos commented 1 year ago

This happens for me in production when I upgrade to Next.js 13:

image

Literally just some text. This is on Netlify.

AndrewVos commented 1 year ago

This seems to be related to the middleware workaround linked to in the docs:

image

https://nextjs.org/docs/advanced-features/i18n-routing

hrasoa commented 1 year ago

@balazsorban44 as you introduced that breaking change, what is your recommendation to use NextResponse outside edge functions ?

BolajiAyodeji commented 1 year ago

Same issue for me with Next13 on Netlify.

Screenshot 2022-11-11 at 7 04 33 PM
taiwabisabi commented 1 year ago

Same issue for me with nextJS13 on Netlify with my middleware :

image
kristojorg commented 1 year ago

Happening when testing middleware with jest. It was working with previous patch versions of Next 13 and then stopped.

mdotwills commented 1 year ago

Hey awesome team, any update on a fix for this? Thanks

vincentdnl commented 1 year ago

Having the same problem when testing my middleware with Jest. Did the @firatgh and the test doesn't throw an error anymore but this is not optimal.

jklein131 commented 1 year ago

I wrote this workaround for jest using mocks. Used it before the inputs, and it creates the correct response objects for a redirect.

jest.mock("next/server", () => ({
  __esModule: true,
  ...jest.requireActual("next/server"),
  NextResponse: {
    // https://github.com/node-fetch/node-fetch/issues/251
    redirect: (t: any, s: number) => {
      const headers = new Headers();
      return {
        status: s,
        ...NextResponse,
        headers: {
          // Location: t.toString(),
          get: (s: string) => {
            if (s === "Location") {
              return t.toString();
            }
          },
        },
        cookies: new ResponseCookies({
          ...headers,
          get: headers.get,
          delete: headers.delete,
          append: headers.append,
          getAll: () => [],
        } as any),
      };
    },
  },
}));
github-actions[bot] commented 1 year ago

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.