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 …</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.