Roblou
Roblou

Reputation: 35

React testing error 'unable to find element with the text

I'm struggling to get my 2nd React test to pass.

Here is my App.js file:


import logo from './logo.svg';
import './App.css';
import {useState} from 'react';
import {Link, Route, Routes} from "react-router-dom";
import Home  from "./pages/home.js";
import Products  from './pages/products.js';
import { HashRouter } from 'react-router-dom';
import Nav from './nav.js'
import './MaterialDesign-Webfont-master/css/materialdesignicons.css';


function App() {

const productArray = []

productArray[0] = new Image()
productArray[0].src = 'imgs/smartphone-one.jpg'
productArray[0].dataset.key = '0'
productArray[0].alt = 'Blueberry 5000 S'
productArray[0].dataset.price = '£499.99'


productArray[1] = new Image ()
productArray[1].src = 'imgs/smartphone-two.jpg'
productArray[1].dataset.key = '1'
productArray[1].alt = 'Banana 12'
productArray[1].dataset.price = '£999.99'

productArray[2] = new Image()
productArray[2].src = 'imgs/smartphone-three.jpg'
productArray[2].dataset.key = '2'
productArray[2].alt = 'Meg 3'
productArray[2].dataset.price = '£799.99'

productArray[3] = new Image ()
productArray[3].src = 'imgs/smartphone-four.jpg'
productArray[3].dataset.key = '3'
productArray[3].alt = 'Communicator 5000'
productArray[3].dataset.price = '£399.99'

productArray[4] = new Image ()
productArray[4].src = 'imgs/smartphone-five.jpg'
productArray[4].dataset.key = '4'
productArray[4].alt = 'Simplex 2'
productArray[4].dataset.price = '£99.99'

  return ( 
<HashRouter>
<Nav/> 
<div>
  <Routes>

<Route path="/" element={<Home/>} />
<Route path="/products" element={<Products 
prods={productArray}/>}

/>
  </Routes>
  </div>
  </HashRouter>
  )
}

export default App;

Here is my Products component file rendered in App above:

import React from 'react-dom'

function Products({prods}) {
 
    return (
        <>
        <h2 className="products-title">Products</h2>
<div className="products" data-testid="products-div">
        {prods.map(prod => {
         return <div className="product" style = {{
            backgroundImage: `url("${prod.src}")`,
            width: 400,
            height: 400,
       display: 'flex',
       flexFlow: 'column nowrap',
       alignItems: 'center',
            backgroundRepeat: 'no-repeat',
            backgroundPosition: 'top',
            backgroundSize: 'contain',
      
        }} id = {prod.dataset.key} >  
    <p className="prod-copy">{prod.alt}</p>
    <p className="prod-price">{prod.dataset.price}</p>
    
              <button className="add">Add to cart</button>
        </div>
       
        })}
         
        </div>
      
        </>
    )

  }
  
  export default Products;

And here is my App.test.js file

import { render, screen } from '@testing-library/react';
import App from './App';
import React from "react";
import Products from './App'


test('Product in nav',  () => {
 
  render(<App />)
expect(screen.getByText('Products')).toBeInTheDocument()

});



test('phones',  () => {
 
  render(<App />)
expect(screen.getByText('£499,99')).toBeInTheDocument()

});

The first test passes, it gets the string 'Products' from the nav bar. There is a div lower down in the DOM with heading 'Products' also - but it doesn't find this when I test by dataset-id - the same with how the second test fails. I've searched high and low but cannot find a solution. I've tried async and await.

Most interestingly, when I do screen.debug(), it doesn't seem to be picking up my <Products /> component, it's like it's not rendered in the DOM when it is I can see all the content on the page loaded. It picks up the <Home/> component though which loads in the <Routes> - is it something to do with this?

For the 2nd test to pass

Upvotes: 3

Views: 131

Answers (1)

Drew Reese
Drew Reese

Reputation: 202618

Generally speaking, node.js test environments are not DOM environments, and since the App component is rendering the router, you've no control over what the current URL is to set the path to "/products" so the Products component is rendered.

You've a couple options:

  1. Unit test Products correctly in isolation.

    test('phones', () => {
      const productArray = [.......];
    
      render(<Products prods={productArray}/>);
      expect(screen.getByText('£499,99')).toBeInTheDocument();
    });
    
  2. Refactor the App component so it's easier to provide a different router for testing purposes. Basically this means removing the HashRouter from the App component.

    import logo from './logo.svg';
    import './App.css';
    import { useState } from 'react';
    import { Link, Route, Routes } from "react-router-dom";
    import Home  from "./pages/home.js";
    import Products  from './pages/products.js';
    import Nav from './nav.js'
    import './MaterialDesign-Webfont-master/css/materialdesignicons.css';
    
    function App() {
      const productArray = []
    
      ...
    
      return ( 
        <>
          <Nav /> 
          <div>
            <Routes>
              <Route path="/" element={<Home />} />
              <Route
                path="/products"
                element={<Products prods={productArray} />}
              />
            </Routes>
          </div>
        </>
      )
    }
    
    export default App;
    

    In the actual app code you still import the HashRouter and wrap App in order to provide a routing context.

    import { HashRouter } from 'react-router-dom';
    
    ...
    <HashRouter>
      <App />
    </HashRouter>
    

    For unit testing purposes import and use the MemoryRouter and provide the initial routes and the current route index.

    import { render, screen } from '@testing-library/react';
    import { MemoryRouter } from 'react-router-dom';
    import App from './App';
    import React from "react";
    import Products from './App';
    
    test('Product in nav', () => {
      render(
        <MemoryRouter initialEntries={["/products"]}>
          <App />
        </MemoryRouter>
      );
      expect(screen.getByText('Products')).toBeInTheDocument();
    });
    
    test('phones', () => {
      render(<MemoryRouter initialEntries={["/products"]}>
          <App />
        </MemoryRouter>
      );
      expect(screen.getByText('£499,99')).toBeInTheDocument();
    });
    

Upvotes: 0

Related Questions