reactjs - 在 React 和 Jest 中测试 Axios 获取请求
问题描述
我是 React 测试的新手,基本上想知道如何模拟来自 Axios 的成功获取请求的行为。
任何帮助将不胜感激,如果您需要我提供更多信息,请告诉我(我只发布了相关代码)。或者,如果有人知道使用 Axios 进行 React 测试的任何有用链接,也将不胜感激!:)
考试
it("Submit button should make a fetch request return a given Github user.", () => {
render(<FavouriteLanguage />);
let mock = new MockAdapter(axios);
userEvent.click(screen.getByText("Submit"));
const searchGithubUsers = mock.onGet(
`https://api.github.com/users/thatguy560/repos?per_page=100`,
{ language: "JavaScript" }
);
expect(searchGithubUsers).toHaveBeenCalled();
});
代码
import { Component } from "react";
import axios from "axios";
import sortKeysByValue from "sort-keys-by-value";
import Swal from "sweetalert2";
import "./css/FavouriteLanguage.css";
class FavouriteLanguage extends Component {
constructor(props) {
super(props);
this.state = {
searchedUsername: "",
programmingLanguagesUsed: "",
};
}
searchGithubUsers = (event) => {
this.setState({ searchedUsername: event.target.value });
};
getData = () => {
let username = this.state.searchedUsername;
axios
.get(`https://api.github.com/users/${username}/repos?per_page=100`)
.then((res) => {
let allProgrammingLanguages = res.data
.map((data) => data.language)
.filter((lang) => lang !== null);
this.setState({ programmingLanguagesUsed: allProgrammingLanguages });
Swal.fire({
title: `${
username[0].toUpperCase() + username.slice(1).toLowerCase()
}'s Favourite Programming Language Is:`,
text: `${this.determineFavouriteLanguage()}`,
width: 625,
});
})
.catch((error) => {
Swal.fire({
title: `${this.DisplayErrorInfo(error)}`,
icon: "error",
});
});
};
render() {
return (
<div className="App">
<header className="App-header">
<div id="wrap">
<h2>Find a user's favourite programming language</h2>
<p>Please enter a valid Github username:</p>
<form onSubmit={this.getData}>
<input
type="text"
placeholder="Github username..."
onChange={this.searchGithubUsers}
/>
</form>
<button
id="Submit"
type="button"
className="btn"
onClick={this.getData}
>
Submit
</button>
</div>
</header>
</div>
);
}
}
export default FavouriteLanguage;
错误
Matcher error: received value must be a mock or spy function
Received has type: object
Received has value: {"abortRequest": [Function abortRequest], "abortRequestOnce": [Function abortRequestOnce], "networkError": [Function networkError], "networkErrorOnce": [Function networkErrorOnce], "passThrough": [Function passThrough], "reply": [Function reply], "replyOnce": [Function replyOnce], "timeout": [Function timeout], "timeoutOnce": [Function timeoutOnce]}
解决方案
You're on right track! I'd recommend moving your MockAdapter
into its own file (for reusability):
utils/mockAxios.js
import MockAdapter from "axios-mock-adapter";
import axios from "axios";
const mockAxios = new MockAdapter(axios);
export default mockAxios;
Then you can import it, add some mocks to it (you can chain axios responses with replyOnce
and/or reply
to test both failing/successful API responses, see chaining is also supported), and then make assertions against the result (in this case, spy on Swal.fire
and make assertions on how its being called):
__tests__/FavouriteLanguage.js
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Swal from "sweetalert2";
import FavouriteLanguage from "./FavouriteLanguage";
import mockAxios from "./utils/mockAxios";
// spying on Swal.fire, turning into a "spy" mocked function
const mockSpy = jest.spyOn(Swal, "fire");
const username = "example";
const API_URL = `https://api.github.com/users/${username}/repos?per_page=100`;
// chaining axios responses...
mockAxios
.onGet(API_URL)
.replyOnce(404) // at first, it fails to fetch
.onGet(API_URL)
.reply(200, [{ language: "Javascript" }]); // then it successfully returns an array of objects, where each object refers to the user's projects; in this simple example, I'm just returning a single project
beforeEach(() => {
mockSpy.mockClear(); // clear any previous calls to this spy mock
});
afterAll(() => {
mockSpy.mockRestore(); // restore spy mock to original Swal.fire
});
it("fails to make an API request to retrieve a github user and displays a sweet alert error", async () => {
render(<FavouriteLanguage />);
// updates input with "example" username
userEvent.type(screen.getByTestId("username"), username);
// submits the form
userEvent.click(screen.getByTestId("submit"));
// using "waitFor" because submitting the form calls an async request
// therefore you need to "waitFor" the request to resolve
await waitFor(() => {
// expect Swal.fire to be called with an error
expect(Swal.fire).toHaveBeenCalledWith({
title: "Error: Request failed with status code 404",
icon: "error",
});
});
});
it("makes an API request to retrieve a github user's details and displays a sweet alert with user's favorite language.", async () => {
render(<FavouriteLanguage />);
userEvent.type(screen.getByTestId("username"), username);
userEvent.click(screen.getByTestId("submit"));
await waitFor(() => {
// expect Swal.fire to be called with user's favorite language
expect(Swal.fire).toHaveBeenCalledWith({
text: "Javascript",
title: "Example's Favourite Programming Language Is:",
width: 625,
});
});
});
I tweaked your code a bit to make the above work, but you can retweak it to work with your code:
FavouriteLanguage.js
import { Component } from "react";
import axios from "axios";
// import sortKeysByValue from "sort-keys-by-value";
import Swal from "sweetalert2";
// import "./css/FavouriteLanguage.css";
class FavouriteLanguage extends Component {
constructor(props) {
super(props);
this.state = {
searchedUsername: "",
programmingLanguagesUsed: "",
};
}
searchGithubUsers = (event) => {
this.setState({ searchedUsername: event.target.value });
};
getData = (e) => {
e.preventDefault(); // make sure to call e.preventDefault when submitting a form; otherwise, it'll refresh the page
const username = this.state.searchedUsername;
axios
.get(`https://api.github.com/users/${username}/repos?per_page=100`)
.then((res) => {
let allProgrammingLanguages = res.data
.map((data) => data.language)
.filter((lang) => lang !== null);
this.setState({ programmingLanguagesUsed: allProgrammingLanguages });
Swal.fire({
title: `${
username[0].toUpperCase() + username.slice(1).toLowerCase()
}'s Favourite Programming Language Is:`,
text: `${res.data[0].language}`,
width: 625,
});
})
.catch((error) => {
Swal.fire({
title: `${error.toString()}`,
icon: "error",
});
});
};
render() {
return (
<div className="App">
<header className="App-header">
<div id="wrap">
<h2>Find a user's favourite programming language</h2>
<p>Please enter a valid Github username:</p>
<form onSubmit={this.getData}>
<input
data-testid="username"
type="text"
placeholder="Github username..."
onChange={this.searchGithubUsers}
/>
<br />
{/*
Since you're already using a form,
put the button inside of it and use type="submit".
Clicking the "submit" button calls "this.getData"!
*/}
<button data-testid="submit" type="submit" className="btn">
Submit
</button>
</form>
</div>
</header>
</div>
);
}
}
export default FavouriteLanguage;
推荐阅读
- r - 无法从 stats.nba.com 提取 JSON 数据
- reactjs - React/tsx:孩子无法从父母那里推断出正确的泛型类型
- java - @Order 带有 @Bean 注释的方法行为
- ruby-on-rails - RSpec 匹配响应
- php - 使用 PHP Mysql 将数据转换为图表
- javascript - 在不知道其分配的变量名称的情况下访问所有导入
- apache-kafka - Kafka SQL API 花费太多时间
- node.js - xml 到 json,有没有办法使用属性的值作为更容易查找的键?
- excel - 如何将循环If语句的结果存储在另一个工作表列中?
- android - 我不明白这是如何工作的(MATCH_PARENT at View with layout_weight)