如何在 Nextjs 应用程序中模拟服务器端 API 调用?

chi*_*imo 9 testing next.js msw

我试图弄清楚在使用 React 测试库测试下一个 js 应用程序时如何模拟对 auth0 身份验证后端的调用。我正在使用auth0/nextjs-auth0来处理身份验证。我的目的是使用MSW为所有 API 调用提供模拟。

我按照 nextjs 文档next.js/examples/with-msw中的示例为客户端和服务器 API 调用设置模拟。auth0/nextjs-auth0包(/api/auth/login、和)生成的所有 API 调用都/api/auth/callback收到模拟响应。/api/auth/logout/api/auth/me

模拟响应/api/auth/me如下所示

import { rest } from 'msw';

export const handlers = [
  // /api/auth/me
  rest.get(/.*\/api\/auth\/me$/, (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        user: { name: 'test', email: 'email@domain.com' },
      }),
    );
  }),
];
Run Code Online (Sandbox Code Playgroud)

当我在浏览器中运行该应用程序时,示例设置工作正常。但是当我运行测试时,模拟没有被拾取。

示例测试块如下所示

import React from 'react';
import {render , screen } from '@testing-library/react';

import Home from 'pages/index';
import App from 'pages/_app';

describe('Home', () => {
  it('should render the loading screen', async () => {
    render(<App Component={Home} />);
    const loader = screen.getByTestId('loading-screen');
    expect(loader).toBeInTheDocument();
  });
});
Run Code Online (Sandbox Code Playgroud)

App我像这样在组件内渲染页面<App Component={Home} />,以便我可以访问包装页面的各种上下文。

我花了大约两天的时间尝试各种配置,但我仍然不知道我可能做错了什么。感谢任何帮助。

Sve*_*eur 5

对于作者来说,这可能已经解决了,但是由于我遇到了同样的问题并且找不到有用的文档,这就是我解决端到端测试的方法:

覆盖/配置 API 主机。


计划是让测试运行器启动 next.js 作为自定义服务器,然后让它响应两个 next.js 作为 API 路由。


其工作的一个要求是能够指定 API 正在调用的后端(主机)(通过环境变量)。然而,对 Next.js 中环境变量的访问是有限的,使用next.config.mjs. 在该文件中,您可以使用运行时环境变量,然后将其绑定到配置对象的 publicRuntimeConfig 部分。

/** @type {import('next').NextConfig} */
const nextConfig = {
  (...)
  publicRuntimeConfig: {
    API_BASE_URL: process.env.API_BASE_URL,
    API_BASE_PATH: process.env.API_BASE_PATH,
  },
  (...)
};

export default nextConfig;
Run Code Online (Sandbox Code Playgroud)

在我引用 API 的每个地方,我都使用 publicRuntimeConfig获取这些值,这使我能够控制(后端)到底在调用什么。


允许在运行时控制 API 的主机名允许我将其更改为本地计算机主机,然后拦截并使用固定装置响应调用。


将 Playwright 配置为测试运行程序。

我的 e2e 测试堆栈基于 Playwright,其中有一个playwright.config.ts文件:

import type { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
  globalSetup: './playwright.setup.js',
  testMatch: /.*\.e2e\.ts/,
};

export default config;
Run Code Online (Sandbox Code Playgroud)

这会调用另一个playwright.setup.js配置实际测试和后端 API 模拟的文件:

import {createServer} from 'http';
import {parse} from 'url';
import next from 'next';
import EndpointFixture from "./fixtures/endpoint.json";

// Config
const dev = process.env.NODE_ENV !== 'production';
const baseUrl = process?.env?.API_BASE_URL || 'localhost:3000';

// Context
const hostname = String(baseUrl.split(/:(?=\d)/)[0]).replace(/.+:\/\//, '');
const port = baseUrl.split(/:(?=\d)/)[1];
const app = next({dev, hostname, port});
const handle = app.getRequestHandler();

// Setup
export default async function playwrightSetup() {
    const server = await createServer(async (request, response) => {
        // Mock for a specific endpoint, responds with a fixture.
        if(request.url.includes(`path/to/api/endpoint/${EndpointFixture[0].slug}`)) {
            response.write(JSON.stringify(EndpointFixture[0]));
            response.end();
            return;
        }

        // Fallback for pai, notifies about missing mock.
        else if(request.url.includes('path/to/api/')) {
            console.log('(Backend) mock not implementeded', request.url);
            return;
        }

        // Regular Next.js behaviour.
        const parsedUrl = parse(request.url, true);
        await handle(request, response, parsedUrl);
    });

    // Start listening on the configured port.
    server.listen(port, (error) => {
        console.error(error);
    });

    // Inject the hostname and port into the applications publicRuntimeConfig.
    process.env.API_BASE_URL = `http://${hostname}:${port}`;
    await app.prepare();
}
Run Code Online (Sandbox Code Playgroud)

使用这种设置,测试运行器应该启动一个服务器,该服务器响应 Next.js 中定义的路由以及故意模拟的路由(针对后端),允许您指定要响应的固定装置。


最后的笔记

将 publicRuntimeConfig 与自定义 Next.js 服务器结合使用,可以让您对后端进行的调用有相对较大的控制,但是,它不一定拦截来自前端的调用,现有的前端模拟可能会拦截来自前端的调用。还是有必要的。