如何使用 Jest 测试/模拟 React 组件中的 fetch api?

问题描述 投票:0回答:3

我是测试驱动开发的新手,我遇到了有关测试/模拟 fetch api 的部分。 但我正在努力编写自己的测试。 我构建了一个简单的天气应用程序,只是为了使用笑话来测试/模拟获取。 但测试总是失败。我不断收到如下错误:

无效的挂钩调用。钩子只能在函数组件的主体内部调用。发生这种情况可能是由于以下原因之一: 不仅如此,我不知道我哪里出了问题,所以我来这里寻求有关如何模拟/改进我的测试以使其成功的提示。

这是我的 React 代码:(App.js)

  const [search, setSearch] = useState('');
  const [weather, setWeather] = useState({}); 
  
  const handleChange = (e) => {
    setSearch(e.target.value)
  }

 //function returns a promise
  const WeatherData = async (e) => {
    if (e.key === "Enter") {
      await fetch(`${api.baseURL}weather?q=${search}&appid=${api.key}`)
        .then(data => data.json())
        .then(city => {
          //console.log(city)
          setSearch('')
          setWeather(city)
        })
    }
  }

  const currentDate = (d) => {
    let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    let days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

    let day = days[d.getDay()];
    let month = months[d.getMonth()];
    let year = d.getFullYear();
    let date = d.getDate();

    return `${day} ${date} ${month} ${year}`

  }

  return (
    <div className="App">
      <h2>International Weather</h2>
      <div className="wrapper">
        <input type="text" id="search-field" placeholder='Search...' onChange={handleChange} onKeyPress={WeatherData} />

        {(typeof weather.main != "undefined") ? (

          <div className='weather-box'>
            <h2>{weather.name}, {weather.sys.country}</h2>
            <h2> {currentDate(new Date())} </h2>

            <div id="weather">

              <div className="details" id="degrees">{(weather.main.temp - 273.15).toFixed(2)}°C</div>
              <div className="details" id="clouds">{weather.weather[0].main}</div>

            </div>
          </div>

        ) : (" ")}

      </div>
    </div>
  );
}

还有我的 App.js 代码:

import { render, screen } from "@testing-library/react";
import App from "./App";

//creating a snapshot test to test if the rendered component is the same as the snapshot app
test("snapshot is correct", () => {
  const tree = render(<App />);
  expect(tree).toMatchSnapshot();
});

//test whether the function works
test("fetch works correctly", async () => {
  App(
    JSON.stringify({
      results: [{ user: "mandla", age: 43 }],
    })
  ).then((data) => {
    expect(data).toBe();
  });
});

如果有人能帮助我理解问题以及为什么我的解决方案不起作用,我将不胜感激。

javascript reactjs unit-testing testing jestjs
3个回答
7
投票

您可以通过以下任意方法测试 fetch API。

  1. 模拟获取
// This is the function we'll be testing
async function withFetch() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts')
  const json = await res.json()

  return json
}

// This is the section where we mock `fetch`
const unmockedFetch = global.fetch

beforeAll(() => {
  global.fetch = () =>
    Promise.resolve({
      json: () => Promise.resolve([]),
    })
})

afterAll(() => {
  global.fetch = unmockedFetch
})

// This is actual testing suite
describe('withFetch', () => {
  test('works', async () => {
    const json = await withFetch()
    expect(Array.isArray(json)).toEqual(true)
    expect(json.length).toEqual(0)
  })
})
  1. jest.spyOn
const fetchMock = jest
  .spyOn(global, 'fetch')
  .mockImplementation(() =>
    Promise.resolve({ json: () => Promise.resolve([]) })
  )

describe('withFetch', () => {
  test('works', async () => {
    const json = await withFetch()

    // highlight-start
    expect(fetchMock).toHaveBeenCalledWith(
      'https://jsonplaceholder.typicode.com/posts'
    )
    // highlight-end

    expect(Array.isArray(json)).toEqual(true)
    expect(json.length).toEqual(0)
  })
})

请查看以下链接

https://benjaminjohnson.me/mocking-fetch


0
投票

开发了一个可以为每个测试用例初始化的获取解析器。

type Method = "get" | "options" | "post" | "put" | "patch" | "delete";

// https://httpstat.us
export enum Status {
  OK = 200,
  Created = 201,
  Accepted = 202,
  NonAuthoritativeInformation = 203,
  NoContent = 204,
  ResetContent = 205,
  PartialContent = 206,
  MultipleChoices = 300,
  MovedPermanently = 301,
  Found = 302,
  SeeOther = 303,
  NotModified = 304,
  UseProxy = 305,
  Unused = 306,
  TemporaryRedirect = 307,
  PermanentRedirect = 308,
  BadRequest = 400,
  Unauthorized = 401,
  PaymentRequired = 402,
  Forbidden = 403,
  NotFound = 404,
  MethodNotAllowed = 405,
  NotAcceptable = 406,
  ProxyAuthenticationRequired = 407,
  RequestTimeout = 408,
  Conflict = 409,
  Gone = 410,
  LengthRequired = 411,
  PreconditionFailed = 412,
  RequestEntityTooLarge = 413,
  RequestURITooLong = 414,
  UnsupportedMediaType = 415,
  RequestedRangeNotSatisfiable = 416,
  ExpectationFailed = 417,
  Imateapot = 418,
  MisdirectedRequest = 421,
  UnprocessableEntity = 422,
  Locked = 423,
  TooEarly = 425,
  UpgradeRequired = 426,
  PreconditionRequired = 428,
  TooManyRequests = 429,
  RequestHeaderFieldsTooLarge = 431,
  UnavailableForLegalReasons = 451,
  InternalServerError = 500,
  NotImplemented = 501,
  BadGateway = 502,
  ServiceUnavailable = 503,
  GatewayTimeout = 504,
  HTTPVersionNotSupported = 505,
  VariantAlsoNegotiates = 506,
  InsufficientStorage = 507,
  NetworkAuthenticationRequired = 511,
  Webserverisreturninganunknownerror = 520,
  Connectiontimedout = 522,
  Atimeoutoccurred = 524
}

/**
 * Stub API request, response in test cases.
 * - should be initialized and destroyed within the context of a specific case.
 * - highly customizable
 *
 * <pre>
 *  describe("Fetch API", () => {
 *    let fetchResolver!: FetchResolver;
 *      beforeEach(() => {
 *      fetchResolver = new FetchResolver();
 *    });
 *
 *    it("should load api", async () => {
 *      // stub
 *      fetchResolver.stub( "http://localhost:8080/endpoint", "post", { id: 100 }, { created: true }, 200);
 *      // fetch
 *      fetch("http://localhost:8080/endpoint",
 *        { method: "post", body: JSON.stringify({ id: 100 })}
 *      ).then((response) => {
 *        if (response.ok) {
 *          response.text().then((text) => {
 *            console.log(text); // { created: true }
 *            expect(text).toBeEqual({ created: true });
 *          });
 *        }
 *      });
 *    });
 *
 *    afterEach(() => {
 *      fetchResolver.clear();
 *    });
 *  });
 * </pre>
 *
 * Even though jest executes tests in parallel jest instance,
 * We can't go wrong if stubs are cleaned after its use
 */
export class FetchResolver {
  private mocks: Map<string, Response> = new Map();
  constructor() {
    this.init();
  }

  public stub(
    uri: string,
    method: Method,
    payload: any,
    response: any,
    status: Status
  ) {
    const finalRequest: { input: RequestInfo | URL; init?: RequestInit } = {
      input: uri,
      init: {
        method: method,
        body: JSON.stringify(payload)
      }
    };
    console.log(
      `mocking fetch :::\nrequest ${this.prettyPrint(
        finalRequest
      )} with \nresponse ${this.prettyPrint(response)} ans status ${status}`
    );
    this.mocks.set(
      JSON.stringify(finalRequest),
      new Response(JSON.stringify(response), { status: status })
    );
  }

  private prettyPrint(json: any) {
    return JSON.stringify(json, null, 2);
  }

  public clear() {
    this.mocks.clear();
  }

  private init() {
    jest
      .spyOn(global, "fetch")
      .mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
        const request = {
          input,
          init
        };
        return new Promise((resolve, reject) => {
          let response = this.mocks.get(JSON.stringify(request));
          if (response) {
            resolve(response);
          } else {
            // rejecting here will hurt component initialization
            console.error(
              `mock not implemented :::\nrequest ${this.prettyPrint(request)}`
            );
            // return empty response
            resolve(new Response("{}"));
          }
        });
      });
  }
  public static initialize() {
    let resolver = new FetchResolver();
    resolver.stub(
      "http://localhost:8080/endpoint",
      "post",
      { id: 100 },
      {
        created: true
      },
      200
    );
    fetch("http://localhost:8080/endpoint", {
      method: "post",
      body: JSON.stringify({ id: 100 })
    }).then((response) => {
      if (response.ok) {
        response.text().then((text) => {
          console.log(text); // { created: true }
        });
      }
    });
  }
}

0
投票

这个问题过去已经被问过。我更喜欢使用像 https://github.com/jefflau/jest-fetch-mock

这样的库

该库很灵活,有很多选择。

© www.soinside.com 2019 - 2024. All rights reserved.