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

[NEXT-889] Unable to Test Server Components in Next.js 13 App Dir #47448

Closed
1 task done
defrex opened this issue Mar 23, 2023 · 15 comments · Fixed by #52393 or #54891
Closed
1 task done

[NEXT-889] Unable to Test Server Components in Next.js 13 App Dir #47448

defrex opened this issue Mar 23, 2023 · 15 comments · Fixed by #52393 or #54891
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. locked Testing Related to testing with Next.js.

Comments

@defrex
Copy link

defrex commented Mar 23, 2023

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 22.3.0: Mon Jan 30 20:39:46 PST 2023; root:xnu-8792.81.3~2/RELEASE_ARM64_T6020
Binaries:
  Node: 19.7.0
  npm: 9.5.0
  Yarn: N/A
  pnpm: N/A
Relevant packages:
  next: 13.2.5-canary.14
  eslint-config-next: 13.2.4
  react: 18.2.0
  react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true), Jest (next/jest)

Link to the code that reproduces this issue

https://github.com/defrex/next13-jest-server-bug

To Reproduce

In the linked repo, npm i && npx jest

The repo is just a very basic create-next-app project with Jest installed, using the next/jest config.

Describe the Bug

Any test of a server-only component (or anything with a server-only component in it's import path) fails to load.

Like so,

 FAIL  src/app/layout.test.ts
  ● Test suite failed to run


      × NEXT_RSC_ERR_CLIENT_IMPORT: server-only
       ╭─[/Users/defrex/code/broken-jest-example/src/app/layout.tsx:1:1]
     1 │ import "./globals.css";
     2 │ import "server-only";
       · ─────────────────────
     3 │
     4 │ export async function getLayoutData() {
     5 │   return {
       ╰────

      3 | describe("Layout getData", () => {
      4 |   it("should return data", async () => {
    > 5 |     const data = await getLayoutData();
        |                 ^
      6 |     expect(data.isData).toBeTruthy();
      7 |   });
      8 | });

      at Object.transformSync (node_modules/next/src/build/swc/index.ts:376:25)
      at Object.<anonymous> (src/app/layout.test.ts:5:17)

Expected Behavior

I would expect these files/components to be testable. Or at very least, to be able to import non-component code from related files for the sake of testing.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-889

@defrex defrex added the bug Issue was opened via the bug report template. label Mar 23, 2023
@github-actions github-actions bot added area: app App directory (appDir: true) Testing Related to testing with Next.js. labels Mar 23, 2023
@balazsorban44
Copy link
Member

Related to #47299

@balazsorban44 balazsorban44 changed the title Unable to Test Server Components in Next.js 13 App Dir [NEXT-889] Unable to Test Server Components in Next.js 13 App Dir Mar 23, 2023
@jordanmcdougall
Copy link

I am also experiencing the same issue as @defrex but instead of importing server-only I am importing next/headers in a page.tsx file that is in the app dir.

Testing is raising the same NEXT_RSC_ERR_CLIENT_IMPORT error as above.

Any guidance on how to fix would be much appreciated!!

@red2678
Copy link

red2678 commented Apr 9, 2023

Same issue. Would love to start testing :) Or would I......

@roelandmoors
Copy link

Related to #47299

In that issue the explanation is that @testing-library/react simulates a browser, but I get that error also without that library. I was trying to test some logic that doesn't uses React and should always run on the server.

@philwolstenholme
Copy link
Contributor

philwolstenholme commented Apr 9, 2023

It's more that jsdom simulates a browser (a client), I think. That must trigger some Next.js or React code that checks to see if a React Sever Component is being used outside of a Server context.

The NEXT-889 issue tag in the issue title means that the Next team are tracking this internally, so that's a good sign.

I think one solution will be to have two Jest profiles projects, one for client-side code and one for server-side code. With two profiles projects you can have two different environments, jsdom for the client and then Node for the server.

@roelandmoors
Copy link

A seperate profile is a good idea. Do you have an example that works with Nextjs 13 maybe?

@philwolstenholme
Copy link
Contributor

A seperate profile is a good idea. Do you have an example that works with Nextjs 13 maybe?

No, sorry, not yet.

My team was about to start looking into it but then I saw that Vercel had updated this ticket with a Linear/Jira ticket reference, so we put the idea to the side and will wait for an official docs update.

If you'd like an example of using two Jest profiles then I think the 'Next Right Now' repo has an example of it. I'm on my phone at the moment but will post this comment then go and find and share a URL on my laptop…

@philwolstenholme
Copy link
Contributor

philwolstenholme commented Apr 9, 2023

Ah, I was wrong, it was Blitz, not 'Next Right Now': https://github.com/blitz-js/blitz/blob/canary/packages/blitz/jest-preset.js#L50-L82

Someone has also shared an example of using profiles projects (I got the name wrong in my earlier comment) here: #12411 (comment)

@roelandmoors
Copy link

roelandmoors commented Apr 9, 2023

That github comment is really nice, but even in a Node environment I get the same error.
Below is the config I used. Without import "server-only"; the tests are passing in the server configuration.

// jest.config.mjs
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
})

const clientTestConfig = {
  displayName: "client",
  testMatch: ["/**/*.clienttest.[jt]s?(x)"],
  testEnvironment: "jest-environment-jsdom",
};

const serverTestConfig = {
  displayName: "server",
  testMatch: ["/**/*.servertest.[jt]s?(x)"],
  testEnvironment: "jest-environment-node",
};

// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const config = {
  // Add more setup options before each test is run
  projects: [await createJestConfig(clientTestConfig)(), await createJestConfig(serverTestConfig)()],
};

export default config;

@philwolstenholme
Copy link
Contributor

Ah yeah, server-only does some stuff that won't be affected by just the jsdom vs node environment switch.

jestjs/jest#13967 has some info on this. There are two things we can try (I've not tried these yet, so not sure if they actually work):

Mocking server-only, potentially?: jestjs/jest#13967 (comment)

Someone else pointed out how server-only works (package.json exports, with the error being thrown only if the react-server export is not used), and suggested how to override what export is used in Jest: jestjs/jest#13967 (comment)

@bramarcade
Copy link

Confirming this also affects me. The 'Testing' section of the app beta docs is still greyed out / unpublished, so hopefully a solution is in the pipes.

@kodiakhq kodiakhq bot closed this as completed in #52393 Jul 12, 2023
kodiakhq bot pushed a commit that referenced this issue Jul 12, 2023
### 🧐 What's in there?

At the moment, it is not possible to test code with `import 'server-only'` in app directory.
When trying to load such file in jest (even with `testEnvironment: node`), the error will be:
```
      ● Test suite failed to run··
          x NEXT_RSC_ERR_CLIENT_IMPORT: server-only
           ,-[lib/util.js:1:1]
         1 | /** @jest-environment node */·
         2 |         import 'server-only'
           :         ^^^^^^^^^^^^^^^^^^^^
         3 |         export const PI = 3.14;
           `----·
          at Object.transformSync (node_modules/next/src/build/swc/index.ts:443:25)
          at transformSync (node_modules/next/src/build/swc/index.ts:629:19)
          at Object.process (node_modules/next/src/build/swc/jest-transformer.ts:117:25)
          at ScriptTransformer.transformSource (node_modules/@jest/transform/build/ScriptTransformer.js:619:31)
          at ScriptTransformer._transformAndBuildScript (node_modules/@jest/transform/build/ScriptTransformer.js:765:40)
          at ScriptTransformer.transform (node_modules/@jest/transform/build/ScriptTransformer.js:822:19)·
```

In a nutshell:
- next/swc is looking for ‘server-only’ [text in the source](https://github.com/vercel/next.js/blob/canary/packages/next-swc/crates/core/src/react_server_components.rs#L576), and throw if not configured for server
- next's jest-transformer will only configure next/swc for server [if the environment is node](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/swc/jest-transformer.ts#L88)
- when testing Next.js apps, your jest testEnvironment is most likely jsdom. But you can configure it [per file with docBlock](https://jestjs.io/docs/configuration#testenvironment-string), which jest-transformer ignores because it only reads the top-level configuration.

This PR fixes this, by 
1. reading the docblock to configure next/swc accordingly and bypass its hardcoded guard
2. mocking `server-only` so [it does not throw](https://github.com/vercel/next.js/blob/canary/packages/next/src/compiled/server-only/index.js) when loaded (jest does not read `react-server` export from package.json)

Users would still have to annotate their `server-only` files with `/** @jest-environment node */` in order to test them.

### 🧪 How to test?

There's a full test available: `pnpm testheadless --testPathPattern jest/server-only`

Here is also a minimal reproduction:

<details>
    <summary>app/layout.tsx</summary>

```typescript
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (<html lang="en"><body>{children}</body></html>)
}
```
</details>

<details>
    <summary>app/page.tsx</summary>

```typescript
import { PI } from '@/lib/utils'

export default function Home() {
    return <h1>{PI}</h1>
}
```
</details>

<details>
    <summary>lib/utils.ts</summary>

```typescript
import 'server-only'

export const PI = 3.14
```
</details>

<details>
    <summary>lib/utils.test.ts</summary>

```typescript
import { PI } from './utils'

it('works', () => expect(PI).toEqual(3.14))
```
</details>

<details>
    <summary>jest.config.js</summary>

```typescript
const nextJest = require('next/jest')

module.exports = nextJest({ dir: './' })({ testEnvironment: 'jsdom' })
```
</details>

### ❗ Notes to reviewers

[jest-docblock](https://packagephobia.com/result?p=jest-docblock) with dependencies is only 12.5 kB.


Fixes #47448
@jviall
Copy link

jviall commented Aug 7, 2023

@feugy This problem still seems to be occurring when using vitest--what is the vitest equivalent of configuring server-only code for testing using /** @jest-environment node */?

@nickserv
Copy link
Contributor

nickserv commented Aug 8, 2023

That should also work with Vitest (along with @vitest-environment).

@github-actions
Copy link
Contributor

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

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 22, 2023
@leerob
Copy link
Member

leerob commented Sep 4, 2023

Hey everyone, we have a fix landed on the latest canary (v13.4.20-canary.16). Please let us know if it's working for y'all 🙏 Appreciate your patience.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. locked Testing Related to testing with Next.js.
Projects
None yet
10 participants