داده های تمسخر آمیز برای تست های یکپارچه سازی در React

اخیراً مجبور شدم تعداد زیادی تست واحد و ادغام را با جست و ریکت-تست-کتابخانه برای پروژه جلویی خود بنویسم. پوشش کد را از 50 درصد به 90 درصد افزایش دادیم. فقط می توانم بگویم که چالش برانگیز بود، اما یکی از مشکلاتی که با آن مواجه شدم، تمسخر برخی قلاب ها، عملکردهای خارجی و اجزا بود.
در زیر میتوانید فهرست راهحلهایی را که من استفاده کردم و نتیجهگیریهایی که به آن رسیدم را بیابید. اگر راه بهتری برای انجام همین کار می دانید – ادامه دهید، خوشحال می شوم پیشنهادات شما را بشنوم
آیا باید کدم را تست کنم؟
اجازه دهید ابتدا این نکته را روشن کنیم زیرا می دانم که برخی از توسعه دهندگان در مورد آن شک دارند.
من فکر می کنم تست کد شما نه تنها منطقی است، بلکه اگر می خواهید یک برنامه قابل اعتماد و قابل نگهداری بسازید بسیار مفید است.
آزمایش عملکردها و اجزای خود به شما کمک می کند تا باگ ها را در مراحل اولیه توسعه پیدا کنید و اطمینان حاصل کنید که در سناریوهای مختلف مطابق انتظار عمل می کنند. همچنین میتواند به شناسایی مشکلات مربوط به وابستگیهای اجزاء کمک کند و اطمینان حاصل کند که آنها به درستی ایزوله شده و قابل استفاده مجدد هستند.
بنابراین اگر در مورد آن تردید دارید – آن را دور بیندازید، به شما آسیبی نمی رساند، برعکس، درک کد شما را به میزان قابل توجهی افزایش می دهد!
تست اول
اجازه دهید فناوریهایی را که در مثالهای زیر استفاده میشوند، تعریف کنم. برای توسعه از React (18.2)، Typescript (4.9.3) و Jest (^27) با React Testing Library (13.4.0) برای آزمایش استفاده می شود. اکنون می توانیم کدنویسی را شروع کنیم.
تصور کنید مولفه ای مانند این دارید:
const Users = () => (
<Page testId={Id.UsersPage} className={classes.usersPage}>
<PageTitle title="Users" testId={Id.PageTitle} />
<UsersList testId={Id.UsersList} />
</Page>
);
ما هیچ منطقی در اینجا نداریم، بنابراین بررسی می کنیم که آیا اجزاء پس از رندر قابل مشاهده هستند یا خیر:
it('Users is rendered correctly', () => {
render(<Users />);
expect(screen.getByTestId(E2eId.UsersPage)).toBeVisible();
expect(screen.getByTestId(E2eId.PageTitle)).toBeVisible();
expect(screen.getByTestId(E2eId.UsersList)).toBeVisible();
});
خوب، پس اکنون می دانیم که تمام قسمت های آن Users
جزء ارائه شده است. از نظر تئوری، ما میتوانیم نشانهگذاری را به جای آن آزمایش کنیم، اما نتایج زیادی را نشان نمیدهد، زیرا چنین آزمایشی شکننده خواهد بود و منطق تجاری را که ممکن است در مؤلفهها داشته باشیم بررسی نمیکند.
در زندگی واقعی، آزمون به این آسانی نخواهد بود زیرا در داخل UsersList
ما احتمالاً درخواستی برای لیست کاربران به روش زیر داریم:
import { getUsers } from 'api';
const UsersList = observer(() => {
const [users, setUsers] = useState([]);
...
useEffect(() => {
getUsers().then((response: User[]) => {
setUsers(response)
});
}, []);
...
});
برای آزمایش به مؤلفه بالا نگاه کنید Users
ما باید تماس API را در اینجا و jest.mock
به ما این امکان را می دهد که به راحتی این کار را انجام دهیم.
jest.mock('api', () => ({
getUsers: () => Promise.resolve([])
});
من قصد ندارم بررسی کنم که آیا این تابع فراخوانی شده است یا خیر، اما اگر لازم باشد باید از آن استفاده کنم jest.fn
به جای اجرای فعلی من.
تست قلاب های سفارشی
از نظر تمسخر رایج ترین مورد برای من، این یک قلاب سفارشی است که برای دریافت داده از فروشگاه استفاده کردم. من MobX را برای مدیریت و استفاده از ایالت انتخاب کردم useStore
برای درخواست و دستکاری داده ها قلاب کنید. در اینجا نسخه به روز شده است UsersList
جزء.
const UsersList = observer(() => {
const {
usersStore: { getUsers }
} = useStore();
useEffect(() => {
getUsers();
}, []);
...
});
بیایید سعی کنیم این قلاب را مسخره کنیم و داده ها را برگردانیم:
import { MOCKED_USERS } from 'mocks';
jest.mock('stores/store.context', () => ({
useStore: () => ({
usersStore: {
getUsers: jest.fn(() => Promise.resolve(MOCKED_USERS))
}
})
}))
اگر این را امتحان کنید، می بینید که کار نمی کند.
اول از همه، شما نمی توانید از متغیرهایی که خارج از آن تعریف شده اند استفاده کنید jest.mock
تابع. برای حل آن می توانید هک کنید jest.mock
با استفاده از توابع تو در تو، و در برخی موارد، کار می کند.
با این حال، من به چنین راه حلی متکی نیستم و زمانی که شما نیاز به بازگرداندن مجموعه داده های مختلف برای آزمایش های مختلف دارید، کمکی نمی کند (به جز اگر می خواهید از switch…case استفاده کنید). بنابراین، پس از چند آزمایش، به ایده دیگری رسیدم و تازه وارد کردم getUsers
در فایل تست کار کنید و mockImplementation را به روش زیر دوباره تعریف کنید:
(getUsers as jest.Mock).mockImplementation(() => {
return Promise.resolve(MOCKED_USERS)
});
تنها مشکل این است که … به خوبی کار نمی کند!
اما دلیل متفاوت است – هر بار که کامپوننت دوباره رندر میشود، تابع hook را اجرا میکنیم و یک تابع جدید جستوجو را برمیگردانیم. بنابراین وقتی دوباره آن را در تست تعریف کردم، عملکرد متفاوتی بود.
بنابراین، ما باید راهی برای برگرداندن یک تابع یکسان در تمام مدت پیدا کنیم. خوشبختانه کار چندان سختی نیست، فقط باید تابع mock را خارج از تابع hook تعریف کنیم.
jest.mock('stores/store.context', () => {
const getUsersMock = jest.fn();
return {
useStore: () => {
usersStore: {
getUsers: getUsersMock
}
}
}
});
بیایید نگاهی بیندازیم که بعد از همه تغییرات چه چیزی در آزمایش خواهیم داشت
it('triggers getUsers when component is mounted', async () => {
const {
usersStore: { getUsers }
} = useStore();
(getUsers as jest.Mock).mockImplementation(() => {
return Promise.resolve(MOCKED_USERS)
});
render(<UsersList />);
await waitFor(() => expect(screen.getByTestId(Id.UsersListContent)).toBeVisible());
expect(getUsers).toBeCalled();
});
تست قلاب ها و وابستگی های خارجی
بعد از اینکه مشکل را با قلاب ها حل کردیم، بقیه موارد دشوار به نظر نمی رسند. در زیر موقعیتهای بسیار رایجی را که نیاز دارید وابستگیهای خارجی مؤلفهها را مسخره کنید، مرور میکنم. تصور کنید ما جزء داریم:
import { useAuth0 } from '@auth0/auth0-react';
import TreeView from 'components/TreeView';
import { notification } from 'antd';
const SecurePage = observer(({ children }: SecurePageProps) => {
const { isAuthenticated, user } = useAuth0();
const onError = () => notification.open(
type: 'error',
message: 'Something went wrong'
});
...
return (
<div>{isAuthenticated ? children : <LoginPage onError={onError} />}
<TreeView />
</div>
);
});
برای پوشش سناریوهای کاربر مجاز و غیرمجاز، ابتدا مسخره می کنم auth0
. سپس، من یک پیاده سازی ساختگی از آن را اضافه می کنم useAuth0
در هر یک از تست ها قلاب کنید. این رویکرد به من اجازه می دهد تا نتایج مختلف را شبیه سازی کنم و اطمینان حاصل کنم که برنامه در هر سناریو به درستی عمل می کند.
jest.mock('@auth0/auth0-react');
...
(useAuth0 as jest.Mock).mockImplementation(() => ({
isAuthenticated: true,
user: {}
}))
بنابراین، ما هر دو مورد را که کاربر مجاز است و غیر مجاز را پوشش می دهیم. اما احتمالاً مایلیم نمایش اعلان خطا را نیز آزمایش کنیم، بنابراین مسخره خواهم کرد antd
و تایید کنید notification.open
اجرا می شود:
import { notification } from 'antd';
...
jest.mock('antd');
...
expect(notification.open).toBeCalled();
یکی دیگر از مواردی که ممکن است در برخی موارد مفید باشد، تمسخر اجزا است. من دارم TreeView
در SecurePage
جزء. فرض کنید من نمی خواهم آن را آزمایش کنم، می توانم آن را به روش زیر مسخره کنم:
jest.mock('components/TreeView', () => null)
مواظب باشید از تست رد شدیم TreeView
جزء به طور کامل من فقط می توانم آن را توصیه کنم زمانی که TreeView
مؤلفه قبلاً به طور جداگانه آزمایش شده است یا زمانی که دلیل خاصی برای حذف آن از آزمایش های خاص دارید.
نتیجه
داده های تمسخر آمیز برای آزمایش های شما معمولاً چیزی ساده به نظر می رسد و توسعه دهندگان توجه زیادی به آن نمی کنند. اما در واقعیت، این یک تکنیک حیاتی برای اطمینان از قابلیت اطمینان و پایداری تستهای شما است. تمسخرهای بد می تواند به شما حس قابل اعتمادی فریبنده بدهد، بنابراین باید مراقب باشید.
برعکس، استفاده صحیح از دادههای ساختگی میتواند به شما کمک کند مشکلات را سریعتر و مؤثرتر جداسازی و اشکالزدایی کنید. با در نظر گرفتن آن، تست های یکپارچه سازی را ایجاد خواهید کرد که قوی و دقیق هستند و تجربیات کاربری با کیفیت بالا را برای مشتریان خود ارائه می دهند.