testing - 如何使用 Jest 在 Next 中测试 _document

标签 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()
    }
  }
}

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

最佳答案

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

文件 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

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

    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 注入(inject)其中,这与您可能注入(inject)到实际应用程序中的正常值不同。

    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;

因为我使用一个 Provider 来通过 React.useContext 管理状态,并为 MaterialUI 主题使用一个 Provider,然后我将它们添加到级联中,能够传递额外的 Prop ,并渲染子组件里面。

关于testing - 如何使用 Jest 在 Next 中测试 _document,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67629091/

相关文章:

javascript - 访问选项值 webdriver.io

testing - 使用具有动态行为的 EasyMock 模拟 void 函数

javascript - Jest 快照测试 - 在 componentDidMount() 中创建新对象时发生错误

node.js - 使用外部 babel 配置会破坏 Node/React 应用程序 - 内部服务器错误

javascript - 使用 getStaticPaths 参数时如何键入 getStaticProps 函数?

java - Junit-如何测试参数的无效使用

testing - 用户验收测试(UAT)和端到端(E2E)测试是一回事吗?

node.js - jest localStorage 实现从何而来?

typescript - 如何期望字符串以指定的变量开头

android - 被阻止 :csp in Android webView