【问题标题】:How to test _document in Next using Jest如何使用 Jest 在 Next 中测试 _document
【发布时间】:2022-05-11 04:45:30
【问题描述】:

我正在尝试在一个项目中实现 100% 的覆盖率,这是我无法测试的唯一文件,因为我不知道如何去做。

我什至不知道从哪里开始。

我正在使用 Jest 和 React 测试库。该项目使用 NextJS。

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      }
    } finally {
      sheet.seal()
    }
  }
}

ps:我知道覆盖率不是最重要的,但对于这个项目来说,100% 是必需的。

【问题讨论】:

  • 我正在寻找同样的东西。我尝试了浅层和安装,但错误:TypeError: Cannot destructure property 'inAmpMode' of '(0 , _react.useContext)(...)' as it is null.。如果你得到什么,请告诉我。如果我先做,我会及时通知你。
  • Shallow 用于正常的 html 渲染。但是当您挂载或调用.html() 时,它会从下一个文档包中的&lt;Html&gt; 组件中给出上述错误。现在我的目标是静态getInitialProps 并试图覆盖这些线条。
  • 我发现了其他投诉,最后在文件顶部找到了/* istanbul ignore file */。伤心。
  • 我只是为_document.js 获得了 100% 的覆盖率。我将与您分享,希望它能涵盖您的大部分内容。

标签: testing jestjs next.js


【解决方案1】:

通常使用 NextJS,我们需要测试 2 个案例,Initial/Server Props 部分和 React Component 部分。你的只有getInitialProps。测试可能因配置而异。我会为未来的读者发布我对这两种情况的配置和测试,并希望它可以成为至少涵盖大部分内容的坚实基础。

文件 pages/_document.js

    import React from 'react';
    import Document, { Html, Head, Main, NextScript } from 'next/document';
    import { ServerStyleSheets } from '@material-ui/core/styles';
    
    export default class MyDocument extends Document {
      render() {
        return (
          <Html lang="en">
            <Head>
              <link
                rel="stylesheet"
                href="https://fonts.googleapis.com/css?family=Lato"
              />
            </Head>
            <body>
              <Main />
              <NextScript />
            </body>
          </Html>
        );
      }
    }
    
    MyDocument.getInitialProps = async ctx => {
      // Render app and page and get the context of the page with collected side effects.
      const sheets = new ServerStyleSheets();
      const originalRenderPage = ctx.renderPage;
    
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheets.collect(<App {...props} />),
        });
    
      const initialProps = await Document.getInitialProps(ctx);
    
      return {
        ...initialProps,
        // Styles fragment is rendered after the app and page rendering finish.
        styles: [
          ...React.Children.toArray(initialProps.styles),
          sheets.getStyleElement(),
        ],
      };
    };

文件 __tests__/pages/_document.js

在发布测试文件之前,非常重要的一件事是将上下文存根,MyDocument.getInitialProps = async ctx =&gt; { 中的ctx 并模拟将在文档代码中备份并调用的ctx.renderPage。此调用的结果是另一个函数,也需要在 other 中调用以达到该部分的最大覆盖率。要获得使用什么的提示,您可以简单地在文档中记录 ctx 并查看该函数的外观。 stub 和 mock 可以是这样的:

    const ctx = {
      renderPage: (options = {}) => {
        // for coverage, call enhanceApp and App
        if (typeof options.enhanceApp === 'function') {
          // pass a functional component as parameter
          const app = options.enhanceApp(() => <div>App Rendered</div>);
          app();
        }
        return {
          html: <div>App Rendered</div>,
          head: (
            <head>
              <title>App Title</title>
            </head>
          ),
        };
      },
    };

这是完整的测试文件,它也处理浅渲染:

    import { createShallow } from '@material-ui/core/test-utils';
    import MockProviders from '../../tests/MockProviders';
    import MyDocument from '../../pages/_document';
    
    /** @test {Document Component getInitialProps} */
    describe('Document Component getInitialProps', () => {
      const ctx = {
        asPath: '/', // not necessary, but useful for testing _app.js
        res: {
          writeHead: jest.fn(),
          end: jest.fn(),
        }, // not necessary but useful for testing other files
        renderPage: (options = {}) => {
          // for coverage, call enhanceApp and App
          console.log('options', options);
          if (typeof options.enhanceApp === 'function') {
            const app = options.enhanceApp(() => <div>App Rendered</div>);
            console.log('app', app);
            app();
          }
          return {
            html: <div>App Rendered</div>,
            head: (
              <head>
                <title>App Title</title>
              </head>
            ),
          };
        },
      };
    
      it('should return finalize html, head and styles in getInitialProps', async () => {
        const result = await MyDocument.getInitialProps(ctx);
        // Log to see the structure for your assertion if any expectation
        // console.log(result);
        expect(result.html.props.children).toBe('App Rendered');
        expect(result.head.props.children.props.children).toBe('App Title');
        expect(result.styles[0].props.id).toBe('jss-server-side');
      });
    });
    
    /** @test {Document Component} */
    describe('Document Component', () => {
      const shallow = createShallow();
      const wrapper = shallow(
        <MockProviders>
          <MyDocument />
        </MockProviders>
      );
    
      const comp = wrapper.find('MyDocument').dive();
      // console.log(comp.debug());
    
      it('should render Document components Html', () => {
        expect(comp.find('Html')).toHaveLength(1);
        expect(comp.find('Head')).toHaveLength(1);
        expect(comp.find('body')).toHaveLength(1);
        expect(comp.find('Main')).toHaveLength(1);
        expect(comp.find('NextScript')).toHaveLength(1);
      });
    });

编辑 1------- 我的 MockProviders 文件仅用于代码分解,而不是在每个测试中级联添加 Providers 组件,以后如果需要添加另一个 Provider 来更改所有测试文件,那么您只需更改一个 MockProvider 文件。它本身就是一个模拟之王,因为您在测试时在其上注入了自己的 props,这与您可能在实际应用程序中注入的正常值不同。

    import { MuiThemeProvider } from '@material-ui/core/styles';
    import { StateProvider } from '../src/states/store';
    import theme from '../src/themes';
    
    const MockProviders = props => {
      return (
        <StateProvider {...props}>
          <MuiThemeProvider theme={theme}>{props.children}</MuiThemeProvider>
        </StateProvider>
      );
    };
    
    export default MockProviders;

因为我使用了带有React.useContext 的Provider 和MaterialUI 主题的Provider 来管理状态,所以我将它们都添加到级联中,能够传递额外的props,并在其中渲染子组件。

【讨论】:

  • 如果你不将MyDocument 包裹在某种MockProviders(状态)中,那么你不需要使用.dive() 来浅渲染它,因为它已经很浅了渲染。
  • MockProviders 中有什么?
  • MockProviders 包含将 props 向下传递给所有应用程序的所有 Providers 的组合,例如用于 useContext 状态的 Provider、用于 Material UI 的 MuiProvider 等。他们真的不必被嘲笑,这取决于你是否要在特定测试中将它们的值作为存根传递。但是你可以配置它并以你想要的方式命名它。
  • @KeitelDOG,您能否也分享模拟提供程序文件
  • @Gowthaman 我添加了一个例子。但它纯粹与您的应用程序配置有关,如果您不使用全局 Providers,您甚至可能不需要它。
猜你喜欢
  • 2019-10-02
  • 2021-07-02
  • 2021-12-09
  • 2020-05-14
  • 1970-01-01
  • 2017-11-11
  • 2020-06-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多