0

I have this React component:

import React, { useState, useEffect } from "react";
import { BrowserRouter } from "react-router-dom";
import "./App.css";
import Home from "./Home";
import SnackOrBoozeApi from "./Api";
import NavBar from "./NavBar";
import { Route, Routes } from "react-router-dom";
import Menu from "./Menu";
import MenuItem from "./MenuItem";
import AddForm from './AddForm';
import NotFound from './NotFound';
import 'bootstrap/dist/css/bootstrap.min.css';

//renders loading screen if loading, else renders app. 
function App() {
  const [isLoading, setIsLoading] = useState(true);
  const [snacks, setSnacks] = useState([]);
  const [drinks, setDrinks] = useState([]);

  useEffect(() => {
    //get the list of snacks from the API and set state appropriately
    async function getSnacks() {
      setIsLoading(true);
      let loadSnacks = await SnackOrBoozeApi.getGoodies('snacks');
      setSnacks(loadSnacks);
      setIsLoading(false);
    }
    //get the list of drinks from the API and set state appropriately
    async function getDrinks() {
      setIsLoading(true);
      let loadDrinks = await SnackOrBoozeApi.getGoodies('drinks');
      setDrinks(loadDrinks);
      setIsLoading(false);
    }
    getSnacks();
    getDrinks();
  }, []);

  if (isLoading) {
    return <p>Loading &hellip;</p>;
  }

  return (
    <div className="App">
      <BrowserRouter>
        <NavBar />
        <main>
          <Routes>
            <Route path="/" element={<Home snacks={snacks} drinks={drinks}/>} />
            <Route path="/snacks" element={<Menu items={snacks} type="snacks" />} />
            <Route path="/snacks/:id" element={<MenuItem items={snacks} cantFind="/snacks" />}/>
            <Route path="/drinks" element={<Menu items={drinks} type="drinks" />} />
            <Route path='/drinks/:id' element={<MenuItem items={drinks} cantFind="/drinks" />}/>
            <Route path='/add' element={<AddForm setSnacks={setSnacks} setDrinks={setDrinks}/>}/>
            <Route path='*' element={<NotFound />}/>
          </Routes>
        </main>
      </BrowserRouter>
    </div>
  );
}

export default App;

and this test to go with it:

import {render, screen, waitFor, act} from '@testing-library/react';
import {test, expect, vi} from 'vitest';
import App from '../App';
import SnackOrBoozeApi from '../Api';

//mock the api call to gather appropriate data
vi.mock('SnackOrBoozeApi', () => ({
  getGoodies: vi.fn((type) => {
    if (type ==="snacks"){
      return Promise.resolve([    {
            "id": "nachos",
            "name": "Nachos",
            "description": "An American classic!",
            "recipe": "Cover expensive, organic tortilla chips with Cheez Whiz.",
            "serve": "Serve in a hand-thrown ceramic bowl, garnished with canned black olives"
          },
          {
            "id": "hummus",
            "name": "Hummus",
            "description": "Sure to impress your vegan friends!",
            "recipe": "Purchase one container of hummus.",
            "serve": "Place unceremoniously on the table, along with pita bread."
          }]);
    }
    if (type ==='drinks'){
      return Promise.resolve([    {
            "id": "martini",
            "name": "Martini",
            "description": "An ice-cold, refreshing classic.",
            "recipe": "Mix 3 parts vodka & 1 part dry vermouth.",
            "serve": "Serve very cold, straight up."
          },
          {
            "id": "negroni",
            "name": "Negroni",
            "description": "A nice drink for a late night conversation.",
            "recipe": "Mix equal parts of gin, Campari, and sweet vermouth.",
            "serve": "Serve cold, either on the rocks or straight up."
          }]);
    }
  })
}));

test('renders the App component', async () => {
    await act (async () => {render(<App />)});
});

test('it matches snapshot', async () => {
    let app;
    await act(async () => {
      app = render(<App />)
    });
    await waitFor(() => expect(app).toMatchSnapshot());
});

test('it displays loading message', async () => {
  const {getByText} = render(<App/>);
  expect(getByText('Loading', {exact: false})).toBeInTheDocument();
});

test('fetches and displays api data', async () => {

    await act(async () => {
        render(<App />);
    });

    await waitFor(() => expect(screen.getByText('Snacks: 2')).toBeInTheDocument());
    await waitFor(() => expect(screen.getByText('Drinks: 2')).toBeInTheDocument());
});

But the final test is not passing because it seems to be actually calling the API instead of mocking it. It is returning Snacks: 5 and Drinks: 7, which are the numbers in the database. The snapshot test isn't passing either, since it's returning the loading state instead, but I'm more concerned about why my mock isn't working.

I've tried different ways of writing the mock (doMock, returnResolvedValueOnce, etc.) and various combinations of act and await and I'm having no luck. The smoke test and loading state test do pass.

1 Answer 1

0

When it comes to mocking server requests it's recommended to use msw and mock response using server.use().

If you still want to make work this approach, I think importing path is not correct

vi.mock('SnackOrBoozeApi', () => ({

Here it should be a relative path, I don't know the exact environment but from your code, it looks like it should be

vi.mock('/Api/SnackOrBoozeApi', () => ({

or what is the actual relative path called. (p.s If you are using VSCode, you can easily click the relative path to the file itself)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.