如何使用 Jest 测试 Next 中的 _document

Ang*_*ias 5 testing jestjs next.js

我试图在项目中实现 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()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Kei*_*DOG 1

通常使用 NextJS,我们需要测试 2 个案例,初始/服务器 Props 部分和 React 组件部分。你的只有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(),
        ],
      };
    };
Run Code Online (Sandbox Code Playgroud)

文件 __tests__/pages/_document.js

在发布测试文件之前,非常重要的一件事是存根上下文,ctxMyDocument.getInitialProps = async ctx => {模拟ctx.renderPage将在文档代码中备份和调用的内容。此调用的结果是另一个函数,也需要在其他函数中调用该函数才能达到该部分的最大覆盖范围。要获得有关使用内容的提示,您只需在文档中记录 ctx 并查看该函数是什么样的。存根和模拟可以是这样的:

    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>
          ),
        };
      },
    };
Run Code Online (Sandbox Code Playgroud)

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

    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);
      });
    });
Run Code Online (Sandbox Code Playgroud)

编辑1----- 我的MockProviders文件只是用于代码分解,而不是在每个测试中级联添加提供程序组件,然后如果您需要添加另一个提供程序来更改所有测试文件,那么您只需更改那一个 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;
Run Code Online (Sandbox Code Playgroud)

因为我使用 Provider 来管理状态,并使用React.useContextProvider 来管理 MaterialUI 主题,所以我将它们级联添加,能够传递额外的 props,并在内部渲染子组件。