You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试随机返回天气数据API时如何实现findBy多值匹配并解决Mock报错

问题背景
  • 待测试页面渲染来自API的数据,该API会随机返回3种天气对象之一,包含字段如下:
forcast: string;
max: number;
min: number;
description: string;
  • 作为测试与TypeScript初学者,需要解决API返回结果随机的场景下,如何断言页面渲染的预报内容的问题。
  • 现有业务代码Weather.tsx如下:
import axios from 'axios';
import { useEffect, useState } from 'react';
import { IWeather } from '../interfaces/IWeather';

const Weather = () => {
  const [minTemp, setMinTemp] = useState<IWeather[]>([]);
  const [maxTemp, setMaxTemp] = useState<IWeather[]>([]);
  const [forcast, setForcast] = useState([]);

  const fetchWeatherData = async () => {
    const response = await axios.get('http://mock-api-call/weather/get-weather');
    setMinTemp(response.data.result.weather.min);
    setMaxTemp(response.data.result.weather.max);
    setForcast(response.data.result.weather.forcast);
  };

  useEffect(() => {
    fetchWeatherData();
  }, []);

  return (
    <div className="container">
      <p className="forcast">{forcast}</p>
      <p className="temperature">Temperature {`${minTemp} to ${maxTemp}`}</p>
    </div>
  );
};

export default Weather;
  • 现有测试代码Weather.test.tsx如下,尝试用||拼接多个匹配规则匹配所有可能的预报值但不生效:
import { render, screen } from '@testing-library/react';
import Weather from '../Weather';

import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('http://mock-api-call/weather/get-weather', (req, res, ctx) => {
    return res(ctx.json({}));
  }),
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('Weather', () => {
  test('should render forcast for day', async () => {
    render(<Weather />);
    const forecast = await screen.findAllByText(/sunny/i || /overcast/i || /snowing/i);
    expect(forecast).toBeInTheDocument();
  });
});
  • 运行测试时抛出如下错误,页面实际运行时接口数据可以正常渲染:
TypeError: Cannot read properties of undefined (reading 'weather')

      10 |   const fetchWeatherData = async () => {
      11 |     const response = await axios.get('http://mock-api-call/weather/get-weather');
    > 12 |     setMinTemp(response.data.result.weather.min);
         |                                     ^
      13 |     setMaxTemp(response.data.result.weather.max);
      14 |     setForcast(response.data.result.weather.forcast);
      15 |   };
问题根因
  1. Mock接口返回结构不符合预期:MSW拦截接口后仅返回空对象{},和真实接口的result.weather嵌套结构不匹配,访问response.data.result.weather时得到undefined,读取其下属性就会抛出TypeError。测试环境不会自动返回真实业务数据,手动mock的返回结构必须和线上接口完全对齐。
  2. State类型定义错误:根据接口定义,min/max为数字类型、forcast为字符串类型,但代码中给minTemp/maxTemp标注了IWeather[]数组类型,forcast初始值设为空数组,和实际赋值类型不匹配,既会触发TS类型报错,初始渲染时也会展示异常内容。
  3. 文本匹配逻辑错误:||是JS逻辑或运算符,作为函数参数传入时会直接返回第一个真值(即/sunny/i),后续两个正则规则不会被传入查询逻辑,无法匹配另外两种天气情况。
  4. 缺少加载态兜底:接口请求是异步逻辑,组件初次渲染时state为初始值,没有加载判断的情况下,测试可能在接口数据还未渲染完成时就执行查询,导致断言失败。
修复方案

修正后的业务代码(Weather.tsx)

import axios from 'axios';
import { useEffect, useState } from 'react';

const Weather = () => {
  // 修正state类型,设置对应类型的初始值
  const [minTemp, setMinTemp] = useState<number | null>(null);
  const [maxTemp, setMaxTemp] = useState<number | null>(null);
  const [forcast, setForcast] = useState<string>('');

  const fetchWeatherData = async () => {
    const response = await axios.get('http://mock-api-call/weather/get-weather');
    // 增加可选链兜底,避免接口结构异常时页面崩溃
    const weatherInfo = response.data?.result?.weather;
    if (weatherInfo) {
      setMinTemp(weatherInfo.min);
      setMaxTemp(weatherInfo.max);
      setForcast(weatherInfo.forcast);
    }
  };

  useEffect(() => {
    fetchWeatherData();
  }, []);

  // 增加加载态判断,数据未返回时展示加载提示
  if (minTemp === null || maxTemp === null || !forcast) {
    return <div className="container">Loading...</div>
  }

  return (
    <div className="container">
      <p className="forcast">{forcast}</p>
      <p className="temperature">Temperature {`${minTemp} to ${maxTemp}`}</p>
    </div>
  );
};

export default Weather;

修正后的测试代码(Weather.test.tsx)

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

import { rest } from 'msw';
import { setupServer } from 'msw/node';

// 统一定义所有可能的预报值,后续维护只需要改这个数组
const POSSIBLE_FORECASTS = ['sunny', 'overcast', 'snowing'];
// 模拟接口随机返回逻辑,返回结构和真实接口完全对齐
const generateMockWeatherRes = () => {
  const randomForcast = POSSIBLE_FORECASTS[Math.floor(Math.random() * POSSIBLE_FORECASTS.length)];
  return {
    result: {
      weather: {
        forcast: randomForcast,
        min: Math.floor(Math.random() * 10),
        max: Math.floor(Math.random() * 15) + 10,
        description: `${randomForcast} weather`
      }
    }
  }
}

const server = setupServer(
  rest.get('http://mock-api-call/weather/get-weather', (req, res, ctx) => {
    return res(ctx.json(generateMockWeatherRes()));
  }),
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('Weather', () => {
  test('should render forcast for day', async () => {
    render(<Weather />);
    // 等待加载态消失,确认接口数据已经渲染到页面
    await screen.findByText('Loading...');
    // 生成组合正则匹配所有可能的预报值,替代无效的||拼接逻辑
    const forcastPattern = new RegExp(POSSIBLE_FORECASTS.join('|'), 'i');
    const forcastElement = await screen.findByText(forcastPattern);
    expect(forcastElement).toBeInTheDocument();
  });
});

内容的提问来源于stack exchange,提问作者code4cash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 08:48:33