How to test component functionality in jest
P粉949848849
P粉949848849 2023-09-07 22:37:54
0
1
576

Hi, I'm new to unit testing, I have a component that renders a list of products and displays those lists. I also have a function in this component to filter these products by price range. I'm using ReactSlider which basically updates two state variables "priceFrom" and "priceTo" and when these state variables change I trigger a function that filters the products based on those prices. So I wanted to test a similar functionality in jest. Is it possible to joke? If so, how can I achieve this, and if not, what alternative can I take to accomplish unit testing of this component. Thanks. The code for this component is as follows. Note that in the code below, when the ReactSlider updates any value, the filterItem function filters the products and updates the ui.

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}>&#2547;{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}>&#2547;{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粉949848849
P粉949848849

reply all(1)
P粉794851975

I can provide you with some suggestions related to your problem

  • Check out a library that can help you test React elements (such as react-testing-library https://testing-library.com/docs/react-testing-library/intro/)

  • Usually something from another library won't be tested because we assume the library itself has tested it (in this case the ReactSlider library

  • Try to split the component into smaller components so it is easier to test (you can use props) and there will be fewer dependencies inside each component

  • We can divide the tests here into two different tests E2E and Unit test, E2E is when you use a tool that simulates user interaction, here In this case you can simulate the user moving the range slider and see what happens afterwards (for this you can use cypress or selenium) These tools run like a human in the browser and unit tests allow you to test functionality, component inputs and Output, here you can also simulate clicks and interactions, but they are not as powerful as E2E for user interaction, because the unit tests (in this case the joke tool) run the tests in JSDOM, which is a simulation of the real browser (simply put)

To fix this I will do the following

  • Install and view the test library and jest
  • Split the component into smaller components
  • Try skipping the ReactSlider test itself and delegate it to the library
  • With unit testing, you can test the filter that props are passed to it (after separating it into another component), and use a testing library to check how it behaves when different props are passed to it
  • Using Cypress or Selenium you can more deeply test what happens when you move the slider in the browser

Cypress: https://www.cypress.io/
Selenium: https://www.selenium.dev/

Keep in mind that E2E and unit testing are the two approaches that will serve you best depending on what you are looking for, each approach simulates user interaction but has a different engine behind it

Hope it helps you

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!