jestjs - 测试通过代理向后端发送请求的 react 应用程序

标签 jestjs react-testing-library

我有以下简单的示例 React 应用程序,其前端在端口 5000 上运行,后端 Express 服务器在端口 5001 上运行。两者之间的关系是通过 "proxy"建立的:"http://localhost :5001", 在 package.json 中。

在前端,如果单击“提交”,则会向后端的 /sample 路由发送 POST 请求,并将状态 (200) 打印到屏幕上。这一切都运行良好。

我现在想使用react-testing-library(或任何其他类似的包)来测试此行为。我已经设置了以下测试文件;但是,当它运行时,我收到Error: connect ECONNREFUSED 127.0.0.1:80。几乎是测试库没有在正确的位置寻找后端?有没有办法在配置中指定它?

我看到其他一些帖子出现此错误,他们建议在 URL 前面包含一个前导“/”,但我的例子中已经有这个(即/sample/)。 Jest test passed but get Error: connect ECONNREFUSED 127.0.0.1:80 at the end

前端

function App() {
  const [responseStatus, setResponseStatus] = useState<number>();
  const { handleSubmit, register } = useForm();

  const onSubmit = (data: any) => {
    console.log(data);
    fetch('/sample/', {
      method: 'POST',
      body: data,
    }).then((resp) => setResponseStatus(resp.status));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <p>Welcome</p>
      <p>{responseStatus}</p>
      <input {...register('fieldNo1')} />
      <button type="submit" data-testid="submit-button">
        Submit
      </button>
    </form>
  );
}

Express 后端

var router = express.Router();

router.post('/', function (req, res, next) {
  res.sendStatus(200);
});

测试文件

test('sample test', async () => {
  render(<App />);

  const welcomeText = screen.getByText('Welcome');
  expect(welcomeText).toBeInTheDocument();

  const submitButton = screen.getByTestId('submit-button');
  fireEvent.click(submitButton);

  const responseText = await screen.findByText('200');
  expect(responseText).toBeInTheDocument();
});

package.json

{
  "name": "sample",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:5001",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.1.1",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.4.1",
    "@types/node": "^16.11.27",
    "@types/react": "^18.0.6",
    "@types/react-dom": "^18.0.2",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-hook-form": "^7.33.1",
    "react-scripts": "5.0.1",
    "supertest": "^6.2.3",
    "typescript": "^4.6.3",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "set PORT=5000 && react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@types/supertest": "^2.0.12"
  }
}

最佳答案

我也有和你类似的需求;我的解决方案与 @jonrsharpe 在评论中建议的差不多 - 我使用 msw 代理到我的 API(本地或真实)

我更进一步,通过 msw 创建了一个代理处理程序,它既代理并将响应保存到文件中,然后又创建了一个模拟处理程序,它基本上只是在需要时重放记录的响应。

我倾向于在它们之间交换:

  • 根据需要更新模拟负载(和/或针对真实 API 进行测试)
  • 当需要速度和可重复性时(例如在 CI 管道中),请使用模拟版本

这是我的 typescript 代码:

import { rest, setupServer } from 'msw';
import { headersToObject } from 'headers-utils';
import fs from 'fs/promises';

// This proxies everthing to real APIs - useful to update mock-responses
export const proxyAllHandlers = [
    rest.all('/*', async (req, res, ctx) => {
        const proxyUrl = new URL(req.url.pathname, 'https://<my-real-url>')
        const proxy = await ctx.fetch(proxyUrl.href, {
            headers: { authorization: "<auth token if required>" }
        });

        let text = await proxy.text();
        console.log(`${req.method} ${req.url.pathname}`, text);
        try {
            // also save to file
            let filename = `./src/tests/mock-responses/${req.method.toUpperCase()}${req.url.pathname.replaceAll("/", "-")}.json`;
            console.log(`Saving ${filename}...`);
            await fs.writeFile(filename, JSON.stringify(JSON.parse(text), null, '\t'), 'utf-8');
        }
        catch(err) {
            console.error("Error saving!", err);
        }

        return res(
            ctx.status(proxy.status),
            ctx.set(headersToObject(proxy.headers)),
            ctx.body(text)
        )
    }),
];

// This mocks all responses from the apis by replaying previous recorded payloads
export const mockHandlers = [
    rest.all("/*", async (req, res, ctx) => {
        let filename = `./src/tests/mock-responses/${req.method.toUpperCase()}${req.url.pathname.replaceAll("/", "-")}.json`;
        let data = await fs.readFile(filename, 'utf-8');
        return res(ctx.json(JSON.parse(data)));
    })
]

// Use proxyAllHandlers to record all expected requests as mocks and use mockHandlers to mock them
export const server = setupServer(...proxyAllHandlers);
beforeAll(() => {
  // Establish requests interception layer before all tests.
  server.listen()
});

afterAll(() => {
  // Clean up after all tests are done, preventing this
  // interception layer from affecting irrelevant tests.
  server.close()
});

归功于此 github 线程,该线程具有使用 msw 的代理代码:https://github.com/mswjs/msw/discussions/887

关于jestjs - 测试通过代理向后端发送请求的 react 应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72945148/

相关文章:

jestjs - 使用 jest.run() 或 jest.runCLI() 运行所有测试或以编程方式运行 jest 的方法

javascript - 用于 Angular 测试的 JS 堆内存不足

reactjs - 'instanceof' 的右侧不是 React 测试库中的对象

reactjs - 类型 'toBeInTheDocument' 上不存在属性 'Matchers<HTMLElement>'

reactjs - 使用 Jest、redux-mock-store 和 Moxios 测试异步调用

node.js - 如何修复此项目中的 "zone is not defined"

node.js - 在 Jenkins 中运行时,NPM 测试神秘地卡住了

reactjs - 在 react-testing-library 中使用 getByRole 按可访问的名称查询时,如何修复 typescript 错误 'name' 在类型 'ByRoleOptions' 中不存在

reactjs - react 测试库与 userEvent.click 错误行为()警告

react-select - 如何将 data-testid 属性添加到 react-select 组件