Heim > Fragen und Antworten > Hauptteil
Hallo, ich bin neu im Bereich Unit-Tests. Ich habe eine Komponente, die eine Liste von Produkten rendert und diese Listen anzeigt. Ich habe in dieser Komponente auch eine Funktion, um diese Produkte nach Preisklasse zu filtern. Ich verwende ReactSlider, der im Grunde zwei Statusvariablen „priceFrom“ und „priceTo“ aktualisiert, und wenn sich diese Statusvariablen ändern, löse ich eine Funktion aus, die die Produkte basierend auf diesen Preisen filtert. Deshalb wollte ich im Scherz eine ähnliche Funktionalität testen. Kann man Witze machen? Wenn ja, wie kann ich dies erreichen, und wenn nicht, welche Alternative kann ich nutzen, um Unit-Tests dieser Komponente durchzuführen. Danke. Der Code für diese Komponente lautet wie folgt. Beachten Sie, dass im folgenden Code die Funktion filterItem die Produkte filtert und die Benutzeroberfläche aktualisiert, wenn der ReactSlider einen Wert aktualisiert.
import React, { useEffect, useState, useContext } from "react"; import { useParams } from "react-router-dom"; import styles from './productsList.module.css'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; import Backdrop from "../../Others/Backdrop/backdrop"; import Modal from '../../Others/Modal/modal'; import ReactPaginate from 'react-paginate'; import ReactSlider from 'react-slider'; import { disableScroll } from "../../Others/HelperFunction/helperFunction"; import { ContextProvider } from "../../Others/AuthContext/authContext"; import './slider.css'; const ProductsList = () => { const context = useContext(ContextProvider); const productId = useParams().productId; const [sidebar, setSidebar] = useState(false); const [backdrop, setBackdrop] = useState(false); const [products, setProducts] = useState([]); const [filteredProducts, setFilteredProducts] = useState([]); const [error, setError] = useState(false); const [status, setStatus] = useState(''); const [modal, setModal] = useState(false); const [priceFrom, setPriceFrom] = useState(0); const [priceTo, setPriceTo] = useState(10000); const [itemOffset, setItemOffset] = useState(0); const [itemNotFound, setItemNotFound] = useState(false); const itemPerPage = 9; const endOffset = itemOffset + itemPerPage; let pageCount = 0; if (filteredProducts.length){ pageCount = Math.ceil(filteredProducts.length / itemPerPage); } else { pageCount = Math.ceil(products.length / itemPerPage); } const handlePageClick = (event) => { if (filteredProducts.length){ const newOffset = (event.selected * itemPerPage) % filteredProducts.length; setItemOffset(newOffset); } else { const newOffset = (event.selected * itemPerPage) % products.length; setItemOffset(newOffset); } } useEffect(() => { window.scrollTo(0, 0); if (context.data !== undefined){ const product = context.data[productId] !== undefined ? context.data[productId] : []; if (product.length){ setProducts(product); setStatus('success'); } else { setStatus('not found'); } } }, [context.data] ); useEffect(() => { window.scrollTo(0, 0); }, [itemOffset, filteredProducts.length]); useEffect(() => { if (backdrop){ disableScroll(); } else { window.onscroll = () => { } } }, [backdrop]) let defaultView = Array.from(Array(12).keys()).map(item => { return <div key={item} className={styles.defaultItemContainer} id={styles.loader}> <div className={styles.defaultItemImgContainer}> <FontAwesomeIcon icon={faSpinner} spinPulse className={styles.spinnerPulse} /> </div> <div className={styles.loadingName}></div> <div className={styles.loadingLink}></div> </div> }); if (products.length){ if (!itemNotFound && filteredProducts.length){ defaultView = filteredProducts.slice(itemOffset, endOffset).map(item => { return <div key={item._id} className={styles.productsContainer} id={styles.loader}> <a href={`/products/${productId}/${item.name}`} className={styles.productsLink}> <div className={styles.productsImgContainer}> <img src={item.img[0]} alt={item.name} className={styles.productsImg}/> </div> <div className={styles.productsName}>{item.name}</div> <div className={styles.productsPrice}>৳{item.price}</div> </a> </div> }); } else if (itemNotFound) { defaultView = <div className={styles.notFoundContainer}> <h2 className={styles.notFoundHeader}>Nothing found based on your range</h2> </div> } else { defaultView = products.slice(itemOffset, endOffset).map(item => { return <div key={item._id} className={styles.productsContainer} id={styles.loader}> <a href={`/products/${productId}/${item.name}`} className={styles.productsLink}> <div className={styles.productsImgContainer}> <img src={item.img[0]} alt={item.name} className={styles.productsImg}/> </div> <div className={styles.productsName}>{item.name}</div> <div className={styles.productsPrice}>৳{item.price}</div> </a> </div> }); } } else if(status === 'not found') { defaultView = <div className={styles.fallbackContainer}> <h2 className={styles.fallbackHeader}>Nothing found</h2> </div> } const filterItem = () => { if (priceFrom && priceTo) { const filteredData = products.filter(item => Number(item.price) >= priceFrom && Number(item.price) <= priceTo); if (filteredData.length){ setItemNotFound(false) setFilteredProducts(filteredData); setSidebar(false); setBackdrop(false); } else { setItemNotFound(true); setItemOffset(0); setSidebar(false); setBackdrop(false); } } } const resetFilter = () => { setPriceFrom(0); setPriceTo(1000); setFilteredProducts([]); } const openSidebar = () => { if (!sidebar){ setSidebar(true); setBackdrop(true); } } const closeSidebar = () => { if (sidebar){ setSidebar(false); setBackdrop(false); } else { setBackdrop(false); } } let displayStatus = <div className={styles.statusMsgContainer}> <h2 className={styles.statusMsgHeader}>Something went wrong</h2> <p className={styles.statusMsgP}>Please try again</p> <button className={styles.statusMsgBtn} onClick={() => { setModal(false); }}>Ok</button> </div> if (status === 'database error'){ displayStatus = <div className={styles.statusMsgContainer}> <h2 className={styles.statusMsgHeader}>Database error</h2> <p className={styles.statusMsgP}>Please try again or contact the admin</p> <button className={styles.statusMsgBtn} onClick={() => { setError(false); setStatus(''); setModal(false); }}>Ok</button> </div> } return ( <> <Backdrop backdrop={ backdrop } toggleBackdrop={ closeSidebar }/> <Modal modal={modal}> {displayStatus} </Modal> <div className={styles.productsListMain}> <div className={styles.productsListContainer}> <div className={styles.sidebarSwitcher} onClick={ openSidebar }> <p className={styles.sidebarSwitcherP}>Show Sidebar</p> </div> <div className={ sidebar ? `${styles.sidebarContainer} ${styles.on}` : styles.sidebarContainer}> <div className={styles.categoryType}> <h2 className={styles.categoryH2}>Categories</h2> <ul className={styles.sidebarLists}> <a href="/products/Bracelet" className={styles.sidebarLink}><li className={productId === 'Bracelet' ? `${styles.sidebarList} ${styles.active}` : styles.sidebarList}>Bracelets</li></a> <a href="/products/Finger Ring" className={styles.sidebarLink}><li className={productId === 'Finger Ring' ? `${styles.sidebarList} ${styles.active}` : styles.sidebarList}>Finger Rings</li></a> <a href="/products/Ear Ring" className={styles.sidebarLink}><li className={productId === 'Ear Ring' ? `${styles.sidebarList} ${styles.active}` : styles.sidebarList}>Ear Rings</li></a> <a href="/products/Necklace" className={styles.sidebarLink}><li className={productId === 'Necklace' ? `${styles.sidebarList} ${styles.active}` : styles.sidebarList}>Necklace</li></a> <a href="/products/Toe Ring" className={styles.sidebarLink}><li className={productId === 'Toe Ring' ? `${styles.sidebarList} ${styles.active}` : styles.sidebarList}>Toe Ring</li></a> <a href="/products/Other" className={styles.sidebarLink}><li className={productId === 'Other' ? `${styles.sidebarList} ${styles.active}` : styles.sidebarList}>Others</li></a> </ul> </div> <div className={styles.categoryType} id={styles.categoryType2}> <h2 className={styles.categoryH2}>Price Range</h2> <ReactSlider max={10000} className="horizontal-slider" thumbClassName="example-thumb" trackClassName="example-track" value={[priceFrom, priceTo]} ariaLabel={['Lower thumb', 'Upper thumb']} ariaValuetext={state => `Thumb value ${state.valueNow}`} renderThumb={(props, state) => <div {...props}>{state.valueNow}</div>} minDistance={1} onChange={([v1, v2]) => { setPriceFrom(v1); setPriceTo(v2); }} pearling /> <button disabled={!priceFrom || !priceTo} className={styles.filterBtn} onClick={ resetFilter }>Reset</button> <button disabled={(!priceFrom || !priceTo) || (priceFrom >= priceTo)} className={styles.filterBtn} onClick={filterItem}>Apply</button> </div> </div> <div className={styles.ProductsLists}> <h2 className={styles.productHeader}>{products.length ? products[0].category : null}</h2> <div className={styles.productsDisplayContainer}> {defaultView} </div> </div> </div> <ReactPaginate breakLabel="..." nextLabel=">" className={styles.paginationContainer} pageClassName={styles.paginationItem} previousClassName={styles.previousItem} nextClassName={styles.nextItem} activeClassName={styles.paginationActive} disabledClassName={styles.paginationDisabled} onPageChange={handlePageClick} pageRangeDisplayed={5} pageCount={itemNotFound ? 0 : pageCount} previousLabel="<" renderOnZeroPageCount={null}/> </div> </> ) } export default ProductsList;
P粉7948519752023-09-08 22:06:24
我可以为您提供一些与您的问题相关的建议
检查一个可以帮助您测试 React 元素的库(例如react-testing-library https://testing-library.com/docs/react-testing-library/intro/)
通常来自另一个库的东西不会被测试,因为我们假设库本身已经测试过它(在这种情况下是 ReactSlider 库
尝试将组件拆分为更小的组件,这样更容易测试(您可以使用 props),并且每个组件内部的依赖关系会更少
我们可以将这里的测试分为两种不同的测试E2E和单元测试,E2E是当您使用模拟用户交互的工具时,在这种情况下您可以模拟用户移动范围滑块,看看之后会发生什么(为此,您可以使用 cypress 或 selenium)这些工具像人类一样在浏览器中运行,单元测试允许您测试功能、组件输入和输出,在这里您可以也模拟点击和交互,但对于用户交互来说,它们不如 E2E 强大,因为单元测试(在本例中是开玩笑的工具)在 JSDOM 中运行测试,JSDOM 是真实浏览器的模拟(简单来说)
为了解决这个问题,我将执行以下操作
赛普拉斯:https://www.cypress.io/
硒: https://www.selenium.dev/
请记住,E2E 和单元测试是两种为您提供最佳服务的方法,具体取决于您要寻找的内容,每种方法都可以模拟用户交互,但其背后的引擎不同
希望对你有帮助