Итак, я пытался освоить модульное тестирование, но в отношении некоторых компонентов у меня возникли проблемы при попытке установки.
ПРИМЕЧАНИЕ: в моем тесте я импортирую компоненты просто как импорт ProfileModal из "./ProfileModal" и т. Д. c.
ТАКЖЕ: причина, по которой я монтирую родительский компонент, а не дочерний компонент ProfileModal, заключается в том, что по какой-либо причине оба параметра mount / shallow в ProfileModal не отображают поля ввода. Они отображаются только при монтировании родительского ProfilePage.
Моя проблема в настоящее время:
- Я не уверен, почему функции не вызываются должным образом onClick и onSubmit. Он показывает формы ввода et c и кнопку правильно. Но я продолжаю получать
ожидаемый (jest.fn ()). ToHaveBeenCalledTimes (ожидаемый)
Expected number of calls: 1
Received number of calls: 0
Так что для этого, который я хочу разработать самостоятельно. Но я хотел бы понять ваш мыслительный процесс так, как вы бы go сказали об этом: о настройке функции validate (), которую я настроил.
Таким образом, в настоящее время в компоненте ProfilePage он содержит дочерние компоненты ProfileCard, ProfileModal и PasswordModal)
Все это работает должным образом во внешнем интерфейсе, как и должно, но при настройке модульные тесты для него были немного хитрыми в отношении вызовов ax ios, а теперь издеваются над другими функциями.
Тестирование дочернего компонента, с которым я надеялся получить помощь, это конкретно компонент ProfileModal.
Теперь в родительском ProfilePage модал устанавливается как скрытый, если только не нажимается определенная кнопка, чтобы изменить состояние на true. Поэтому для тестирования, я настроил сам тест:
Компонент ProfileModal (который является дочерним по отношению к ProfilePage):
class ProfileModal extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
email: "",
phone_number: "",
profile_url: "",
user: "",
nameErrors: "",
phone_Error: "",
formErrors: {
name: "",
email: "",
phone_number: ""
},
touched: {
name: false,
email: false,
phone_number: false
}
};
}
componentDidMount() {
this.getUserInfo();
}
getUserInfo = async () => {
let authToken =
"Bearer " + Cookies.get("auth-token").replace("__#__", ".");
const instance = axios.create({
baseURL: "http://127.0.0.1:8000/",
timeout: 8000,
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
Authorization: authToken
}
});
await instance
.get("api/session")
.then(json => {
console.log(json);
this.setState({
name: json.data.data.name,
email: json.data.data.email,
phone_number: json.data.data.phone_number,
profile_url: json.data.data.profile_url
});
})
.catch(err => {
console.log(err);
throw err;
});
};
resendEmail = async () => {
const instance = axios.create({
baseURL: "http://127.0.0.1:8000/",
timeout: 8000,
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
}
});
await instance.post("api/auth/signup/resend_activate").catch(err => {
console.log(err);
throw err;
});
};
uploadImage = async e => {
const files = e.target.files;
const data = new FormData();
data.append("file", files[0]);
data.append("upload_preset", "Avatar");
const res = await fetch(
"https://api.cloudinary.com/v1_1/dqw327vag/image/upload",
{
method: "POST",
body: data
}
).catch(err => {
console.log(err);
throw err;
});
const file = await res.json();
this.setState({ profile_url: file.secure_url });
};
submitUpdate = async () => {
let authToken =
"Bearer " + Cookies.get("auth-token").replace("__#__", ".");
const instance = await axios.create({
baseURL: "http://127.0.0.1:8000/",
timeout: 1000,
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
Authorization: authToken
}
});
const id = this.state.email;
instance
.put(`api/users/${id}`, {
user_email: this.state.email,
edit_email: this.state.email,
edit_name: this.state.name,
edit_profile_url: this.state.profile_url,
edit_phone_number: this.state.phone_number
})
.then(json => {
if (json.data.result == 1) {
toastr.success(json.data.message);
location.reload();
}
})
.catch(err => {
console.log(err);
throw err;
});
};
handleChange = e => {
e.preventDefault();
this.setState({
[e.target.name]: e.target.value
});
};
validate = () => {
// let isError = false;
// const errors = {};
let isError = false;
let nameErrors = "";
let phoneError = "";
if (this.state.name.length < 4) {
isError = true;
nameErrors = "Name has to be at least 4 characters";
this.setState({ nameErrors: nameErrors });
}
// if ((this.state.formErrors.name = "has-success")) {
// isError = true;
// }
if (
this.state.phone_number.search(
/^(\+0?1\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/
)
) {
isError = true;
phoneError = "Must have only numbers and be in xxx-xxx-xxxx format";
this.setState({ phone_Error: phoneError });
}
return isError;
};
handleSubmit = e => {
e.preventDefault();
const err = this.validate();
if (!err) {
this.submitUpdate();
}
};
render() {
return (
<div>
<Modal
isOpen={this.props.showProfile}
toggle={this.props.closeProfile}
>
<ModalHeader toggle={this.props.closeProfile}>
Edit Profile
</ModalHeader>
<ModalBody id="profile-modal" className="text-center m-3">
<Container fluid>
<Form className="profile-form">
<FormGroup>
<Label className="d-flex align-self-start">
Name
</Label>
<Input
name="name"
placeholder="Name"
value={this.state.name}
onChange={e => {
this.handleChange(e);
}}
autoComplete="off"
/>
<div style={{ color: "red", fontSize: 12 }}>
{this.state.nameErrors}
</div>
<FormText style={{ textAlign: "left" }}>
Full Name
</FormText>
</FormGroup>
<FormGroup>
<Label className="d-flex align-self-start">
Phone Number
</Label>
<Input
type="phone_number"
name="phone_number"
placeholder="Phone Number"
value={this.state.phone_number}
onChange={e => {
this.handleChange(e);
}}
autoComplete="off"
/>
<div style={{ color: "red", fontSize: 12 }}>
{this.state.phone_Error}
</div>
</FormGroup>
<FormGroup>
<Label className="d-flex align-self-start">
Change Avatar
</Label>
<div>
<input
className="hidden"
id="files"
name="image"
type="file"
placeholder="Upload an avatar"
onClick={this.uploadImage}
/>
<label
htmlFor="files"
className="custom-upload"
>
<FontAwesomeIcon
className="upload-icon"
icon={faCloudUploadAlt}
/>
<strong>Upload Image</strong>
</label>
<br />
<img
id="upload-picture"
className="rounded-circle rounded mr-2 mb-2 hidden"
src={
this.state.profile_url
? this.state.profile_url
: avatar1
}
value={this.state.profile_url}
onChange={this.uploadImage}
width="140"
height="140"
/>
</div>
</FormGroup>
</Form>
</Container>
</ModalBody>
<ModalFooter>
<Button
className="resend-btn"
onClick={() => this.resendEmail()}
>
Resend Email Confirmation
</Button>
<Button
className="update-profile-btn"
onClick={e => this.handleSubmit(e)}
>
Save changes
</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
И тест для этого я бегу. Примечание. Если нет другого способа, мне сначала пришлось смонтировать родительский ProfilePage, чтобы получить доступ к компоненту ProfileModal для тестирования.
describe("Profile Modal Component", () => {
it("checks if handleChange is correctly working", () => {
const wrapper = mount(<ProfilePage handleChange={handleChange} handleSubmit={handleSubmit} />);
wrapper.find("button.profile-btn").simulate("click");
const name = wrapped.find("input[name='name']");
name.instance().value = "name";
name.simulate("change");
wrapped.find("button.update-profile-btn").simulate('click)
expect(handleChange).toHaveBeenCalledTimes(1)
expect(handleSubmit).toHaveBeenCalledTimes(1)
}),
});