نوشتن تست در ReactJs  برای هوک ها

داخل یکی از مقاله های قبلیم در مورد نوشتنن تست برای Reactjs صحبت کردیم . https://virgool.io/@nima.2004hkh/%D9%86%D9%88%D8%B4%D8%AA%D9%86-%D8%AA%D8%B3%D8%AA-%D8%AF%D8%B1-reatcjs-%D8%A8%D9%87-%DA%A9%D9%85%DA%A9-jest-%D9%88-enzyme-uzkagtsqm5uc مشکلی که داشتم چی بود؟ اینکه من میخواستم داخل یک functional component که هوک داره ، یک تست بنویسم . من داخل کامپوننت هم از Material-ui ‌استفاده کردم چون کار باهاش برام لذت بخشه . ( سلیقه ای دیگه . منم بد سلیقم )‌. هرکار میکردم نمیتونستم به سلکتورها برسم . به هر دری زدم و متوجه شدم که برای هوک ها باید کارهای دیگه ای کرد و به شکل دیگه نوشت . موضوع اینه که وقتی ما Functional Component داریم مینویسیم ، Enzyme نمیتونه کامپوننت رو رندر کنه . چون باید حتما کامپوننت یک instance از کلاس باشه . اما ما Function داریم مینویسیم . بعد از کلی زیر و رو کردن فهمیدم باید از یک کتابخانه دیگه استفاده کنیم . کتابخانه react-testing-library همونیه که میتونه کمک کنه به ما . حالا با یک مثال ساده نگاه میندازیم به موضوع : import React, {useState} from 'react' import {withStyles} from '@material-ui/core/styles'; import PropTypes from 'prop-types'; import InputAdornment from '@material-ui/core/InputAdornment'; import TextField from '@material-ui/core/TextField'; import classNames from 'classnames'; import Grid from "@material-ui/core/Grid"; import {isNumber} from "persian-regex"; import Fab from '@material-ui/core/Fab'; const styles = theme => ({ root: { display: 'flex', flexWrap: 'wrap', }, margin: { margin: theme.spacing.unit, height: 39 }, textField: { flexBasis: 200, }, rootGrid: { padding: 0, height: '100%', overflow: 'hidden' }, rootGridItem: { textAlign: 'left' }, adornment: { '& p': { color: '#ffffff', fontSize: 12, display: 'inherit', width: 85, paddingRight: 5 } }, inputLabel: { color: '#ffffff' }, extendedIcon: { marginRight: theme.spacing.unit, }, fabButton: { width: '100%', margin: '0 auto', textAlign: 'center', borderRadius: 5 }, fabButtons: { padding: 6 }, buyFab: { backgroundColor: '#12b886', color: '#ffffff' }, sellFab: { backgroundColor: '#fa5252', color: '#ffffff' } }); function Limit(props) { const [buyAmount, setBuyAmount] = useState(0); const [alert, setAlert] = useState(""); function handleBuy() { setAlert("") if (buyAmount === 0 || buyAmount === "") setAlert("missing inputs") setTimeout(() => { setAlert(""); }, 2000) } function handleChange(event, name) { const amount = event.target.value; if (amount !== undefined) if (isNumber(amount)) switch (name) { case 'buyAmount': setBuyAmount(amount); break; } } const {classes} = props; return ( <React.Fragment> <Grid container classes={{container: classes.rootGrid}}> <Grid item xs={6} className={classes.rootGridItem}> <TextField id="filled-adornment-amount" className={classNames(classes.margin, classes.textField)} variant="filled" InputLabelProps={{ classes: {root: classes.inputLabel} }} value={buyAmount > 0 ? buyAmount : ""} ={(e) => handleChange(e, 'buyAmount')} InputProps={{ inputProps: {'data-testid': "buyAmount"}, classes: {root: classes.inputLabel}, startAdornment: <InputAdornment position="start" classes={{root: classes.adornment}}> Amount (IRR) </InputAdornment>, }} /> </Grid> <Grid item xs={6} className={classNames(classes.rootGridItem, classes.fabButtons)}> <Fab data-testid="buy" classes={{ root: classes.buyFab }} ={handleBuy} variant="extended" size="large" color="primary" aria-label="Buy" className={classes.fabButton} > Buy </Fab> </Grid> <div data-testid="alert">{alert}</div> </Grid> </React.Fragment> ) } Limit.propTypes = { classes: PropTypes.object.isRequired, }; export default withStyles(styles)(Limit);داخل این مثال از material-ui و یکی از پکیج هایی که خودم نوشتم به اسم persian-regex استفاده کردم . حالا میخوام تستم رو درست کنم . میرم داخل پوشه __tests__ و یک فایل به اسم Limit.js ‌میسازم . تست ما از این پوشه پیروی میکنه . داخل ترمینال پکیجمون رو نصب میکنیم : npm i react-testing-library --save-devحالا داخل Limit.js که ساختیم برای تست کد زیر رو میزنیم :import React from 'react'; import Limit from "../Components/Limit"; // limit component we made above import 'react-testing-library/cleanup-after-each' import {render, fireEvent} from 'react-testing-library' import {isNumber} from "persian-regex"; describe('Test Limit Component', () => { describe('Click on Buy Buttons and check validation', () => { describe('should click on Buy Button ', () => { it('should return false , cause inputs are empty', () => { const {getByTestId} = render(<Limit/>) const buyButton = getByTestId('buy'); const buyAmountInput = getByTestId('buyAmount'); const alert = getByTestId('alert'); fireEvent.change(buyAmountInput, {target: {buyAmount: ""}}); fireEvent.click(buyButton) if (buyAmountInput.buyAmount === "") { expect(alert).toContain('missing inputs') } }) it('write into buy Amount input and it should get value : 123456789', () => { const {getByTestId} = render(<Limit/>) const number = '123456789'; const buyAmountInput = getByTestId('buyAmount'); fireEvent.change(buyAmountInput, {target: {buyAmount: isNumber(number) ? number : ''}}) expect(buyAmountInput.buyAmount).toEqual(number) }) it('write into buy Amount input and it should get empty value because input is not valid', () => { const {getByTestId} = render(<Limit/>) const number = 'hi there'; const buyAmountInput = getByTestId('buyAmount'); fireEvent.change(buyAmountInput, {target: {buyAmount: isNumber(number) ? number : ''}}) expect(buyAmountInput.buyAmount).toEqual('') }) }) }) }) داخل کد بالا سه شرط نوشتم . یکی کلیک کردن روی دکمه خرید ، بدون وارد کردن مقدار ، یکی کلیک با مقدار درست یکی هم با مقدار اشتباه و validate کدن input . حالا بعد از اجرای دستور npm run test ، تست یکی یکی اجرا میشه و درست بودنشون چک میشه .