Instead of mocking an axios request, I try to test the component using msw, but after the request I don't get the visibility of the content in the component, what am I doing wrong?
My component
import React, {useEffect, useState} from 'react'
import axios from "axios";
export default function TestPage() {
const [testData, setTestData] = useState('')
useEffect(() => {
const getSomeData = async () => {
const data = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
setTestData(data.data.title)
}
getSomeData()
}, [])
return (
<div className='test'>
<h1>{testData}</h1>
</div>
)
}
My test file
import React from 'react'
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import {render, act, screen} from '#testing-library/react'
import '#testing-library/jest-dom/extend-expect'
import TestPage from "../testPage";
const allUsers = [
{title:'User'}
]
const server = setupServer(
rest.get('https://jsonplaceholder.typicode.com/todos/1', async (req, res, ctx) => {
return res(ctx.json( {data: allUsers} ));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers())
afterAll(() => server.close());
test('loads and displays greeting', async () => {
await act(async () => {
await render(<TestPage/>)
})
await screen.findByText('User') //I have no response content here
screen.debug()
})
You do not need to define the data field for ctx.json(), the resolved value of the axios.get() method has a data field. See Response schema
In addition, the data returned by the API is an array.
You don't need to use the act helper function, wait for the result of the API call operation in your test by using one of the async utilities like waitFor or a find* query is enough.
E.g.
TestPage.tsx:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
export default function TestPage() {
const [testData, setTestData] = useState('');
useEffect(() => {
const getSomeData = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/todos/1');
console.log(res.data);
setTestData(res.data[0]?.title);
};
getSomeData();
}, []);
return (
<div className="test">
<h1>{testData}</h1>
</div>
);
}
TestPage.test.tsx:
import React from 'react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import TestPage from './TestPage';
const allUsers = [{ title: 'User' }];
const server = setupServer(
rest.get('https://jsonplaceholder.typicode.com/todos/1', async (req, res, ctx) => {
return res(ctx.json(allUsers));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('67902700', () => {
test('loads and displays greeting', async () => {
render(<TestPage />);
await screen.findByText('User');
screen.debug();
});
});
test result:
PASS examples/67902700/TestPage.test.tsx (7.499 s)
67902700
✓ loads and displays greeting (65 ms)
console.log
[ { title: 'User' } ]
at examples/67902700/TestPage.tsx:10:15
console.log
<body>
<div>
<div
class="test"
>
<h1>
User
</h1>
</div>
</div>
</body>
at logDOM (node_modules/#testing-library/dom/dist/pretty-dom.js:82:13)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 7.978 s
Related
I'm practicing mocking with a simple React app using Jest + React Testing Library.
I did successfully mock fetch to test the app's functionality but I'm wondering why setting up the mock with beforeEach hook works while with the beforeAll hook my tests fail.
This is the (working) test code:
import {
render,
screen,
waitForElementToBeRemoved,
} from "#testing-library/react";
import App from "./App";
import { mockFetch } from "./mock-fetch";
import { mockResponse } from "./utils";
beforeEach(() => {
jest.spyOn(window, "fetch").mockImplementation(mockFetch);
});
describe("<App />", () => {
it("renders correctly a title", async () => {
render(<App />);
await waitForLoading();
expect(screen.getByText(/list of posts/i)).toBeInTheDocument();
});
describe("when requesting for posts", () => {
it("renders the list of posts once they are successfully fetched", async () => {
render(<App />);
await waitForLoading();
mockResponse.products.forEach((product) => {
expect(screen.getByText(product.title)).toBeInTheDocument();
expect(screen.getByText(product.price)).toBeInTheDocument();
});
});
it("should render an error message when posts fetch is not successful", async () => {
const error = "Error";
window.fetch.mockResolvedValueOnce({
ok: false,
status: 500,
});
render(<App />);
await waitForLoading();
expect(screen.getByText(error)).toBeInTheDocument();
});
it("should render a no posts message if there are no posts to show", async () => {
window.fetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: () =>
Promise.resolve({
products: [],
}),
});
render(<App />);
await waitForLoading();
expect(screen.getByText(/no posts.../i)).toBeInTheDocument();
});
});
});
function waitForLoading() {
return waitForElementToBeRemoved(() => screen.queryByText(/loading.../i));
}
This is the App component:
import { useState, useEffect } from "react";
import Products from "./Products";
import "./App.css";
import { fetchProducts } from "./fetch-products";
function App() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
fetchProducts()
.then((results) => {
setProducts(results.products);
})
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (error) return <div>{error}</div>;
return (
<div>
<h1>List of posts</h1>
{loading ? <p>Loading...</p> : <Products products={products} />}
</div>
);
}
export default App;
When using beforeAll instead of beforeEach the assertions fail because it can't find the elements I'm querying for. Also, I noticed that the fetchProducts function throws, this error is being set on the error state and in the test it's rendering the div containing the error with the message: Cannot read properties of undefined (reading 'ok').
How do I write a unit test in Jest for the initializePlayers function inside the useEffect?
Test if the call is working?
export default function App() {
...
useEffect(() => {
const initializePlayers = async () => {
await axios.get(url)
.then(async res=> {
const activePlayers = res.data.filter(p => p.active === true);
setPlayers(activePlayers);
}).catch(err => {
console.log(err);
})
}
initializePlayerPool();
}, []);
Please try this example.
import React from "react";
import { mount, shallow } from "enzyme";
import axios from "axios";
import { act } from "react-dom/test-utils";
import App from "./App";
jest.mock("axios");
// mock data
const url= "YOUR_URL",
describe("App test", () => {
let wrapper;
// clear all mocks
afterEach(() => {
jest.clearAllMocks();
});
test("load app", async () => {
// mock axios promise
await act(async () => {
await axios.get.mockImplementationOnce(() => Promise.resolve(url));
wrapper = mount(<App />);
});
wrapper.update();
await expect(axios.get).toHaveBeenCalledTimes(1);
});
});
I have component that use thunk action.
And inside the component, I have an asynchronous execution that, after processing, returns the username to the markup, how to lock the result of such an execution
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from "react-redux";
import setTestData from "../redux/asyncServices/testService";
function TestPage() {
const dispatch = useDispatch()
const user = useSelector((state) => state.testReducer.user)
const loading = useSelector((state) => state.testReducer.loading)
useEffect(() => {
dispatch(setTestData())
}, [])
return (
<div className='users_wrapper'>
<div className='container'>
<div className='users_content'>
<div className='title'>
<h1>Test</h1>
</div>
{
!loading ? <h1>{user.name}</h1> : null
}
</div>
</div>
</div>
)
}
export default TestPage
Async action
import { createAsyncThunk } from '#reduxjs/toolkit'
import db from '../../indexedDB/db'
import '../../indexedDB/db.timesheetHooks'
const setTestData = createAsyncThunk(
'setTestData',
async () => {
const user = await db.loggedUser.orderBy('id').last()
return {user}
},
)
And code where I try to mock function result, but something went wrong.I understand how mock simple function in jest, but how mock in this case
jest.mock('../redux/asyncServices/testService', () => {
return {
setTestData: () => ({type: "setTestData/fulfilled", payload: {
user: {name: 'Loaded user name'}
}}),
};
});
describe('Timesheet Menu page tests', () => {
beforeEach(async () => {
matchMedia = new MatchMediaMock()
})
afterEach(() => {
matchMedia.clear()
})
test('Component renders correctly', async () => {
const testFn = require('../pages/TestPage');
jest.spyOn(testFn, 'setTestData').mockReturnValue('c');
await waitFor(() => {
renderWithRedux(<TestPage/>, {initialState})
})
expect(screen.getByText('Test')).toBeInTheDocument()
expect(screen.getByText('Loaded user name')).toBeInTheDocument()
})
})
I am trying to test a simple component.
In my component, I am calling the fetchPdf function with useEffect.
Inside fetchPdf function I am making axios request and if the call was successful I am setting the response (which is the pdf) in the state.
My component:
import React, { useState, useEffect } from "react";
import axios from 'axios';
export default function App() {
const [pdf, setPdf] = useState();
const fetchPdf = () => {
// Here I am make an API call - on success I am
// set the pdf from the response into state
// axios.get('url/endpoint')
// .then((res) => {
// if (res.status === 200) {
// setPdf(res.data);
// }
// }).catch((e) => console.log(e.message));
setPdf("Mocked PDF");
};
useEffect(() => {
fetchPdf();
}, []);
return (
<div className="App">
<h1>My pdf from state: {pdf}</h1>
</div>
);
}
My test:
import React from "react";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";
Enzyme.configure({ adapter: new Adapter() });
describe("<App />", () => {
let wrapper;
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, "useState");
useStateSpy.mockImplementation((init) => [init, setState]);
beforeEach(() => {
wrapper = Enzyme.shallow(<App />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe("calling designPDF", () => {
it("set designPDF into the state", () => {
// Not sure how can I test the fetchPDF function as been called
});
});
});
Codesendbox example
useEffect does not support shallow rendering yet. So you should use Full Rendering API (mount(...)).
Besides, you should mock axios.get method and its resolved value. Call the whenStable function to ensure that the promise returned by the axios.get method is resolved or rejected
E.g.
App.jsx:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function App() {
const [pdf, setPdf] = useState();
const fetchPdf = () => {
axios
.get('url/endpoint')
.then((res) => {
if (res.status === 200) {
setPdf(res.data);
}
})
.catch((e) => console.log(e.message));
};
useEffect(() => {
fetchPdf();
}, []);
return (
<div className="App">
<h1>My pdf from state: {pdf}</h1>
</div>
);
}
App.test.jsx:
import App from './App';
import axios from 'axios';
import { mount } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
const whenStable = async () => {
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
};
describe('65310275', () => {
it('should get pdf', async () => {
const getSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: 'Mocked PDF', status: 200 });
const wrapper = mount(<App></App>);
await whenStable();
expect(wrapper.find('h1').text()).toEqual('My pdf from state: Mocked PDF');
expect(getSpy).toBeCalledWith('url/endpoint');
getSpy.mockRestore();
});
it('should handle error', async () => {
const mErr = new Error('timeout');
const getSpy = jest.spyOn(axios, 'get').mockRejectedValueOnce(mErr);
const logSpy = jest.spyOn(console, 'log');
const wrapper = mount(<App></App>);
await whenStable();
expect(wrapper.find('h1').text()).toEqual('My pdf from state: ');
expect(getSpy).toBeCalledWith('url/endpoint');
expect(logSpy).toBeCalledWith('timeout');
getSpy.mockRestore();
});
});
unit test result:
PASS examples/65310275/App.test.jsx
65310275
✓ should get pdf (40 ms)
✓ should handle error (13 ms)
console.log
timeout
at CustomConsole.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
App.jsx | 100 | 50 | 100 | 100 | 11
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.095 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/65310275
how can i test the child component has mounted. Please see the below implmentation.
i have an api call, until the data resolves it will show a loader then it will show the actual component.
Using mount i need to simulate a click on the child component also. What am i doing wrong here.
Please see the below snippet.
// App.js
import React, {Component, Fragment} from 'react'
import Child from './child'
class App extends Component{
state = {
data: null,
enable: false
}
componentDidMount(){
this.getData()
}
getData = async () => {
const response = await fetch('http://www.example.com');
const data = await response.json();
this.setState({
data
})
}
_handleChildClick = () => {
this.setState({
enable: true
})
}
render(){
const {data, enable} = this.state
if(!data){
return (
<div>
Loading
</div>
)
}else{
<Fragment>
<Child
handleChildClick={this._handleChildClick}
/>
</Fragment>
}
}
}
export default App
import React from 'react';
const child = () => {
return(
<div>
<button
className="toggle"
onClick={props.handleChildClick}
>
Toggle
</button>
</div>
)
}
export default child
// App.test.js
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it('should trigger _handleChildClick', async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () => new Promise((resolve, reject) => {
resolve(
{
name: "some data"
}
)
})
}))
const mountWrapper = await mount(<App />)
mountWrapper.update()
console.log("mountWrapper", mountWrapper.debug()) // showing the loader one
setTimeout(() => {
console.log("mountWrapper", mountWrapper.debug()) // nothing showing
// expect(mountWrapper.find('.toggle').length).toEqual(1)
},0)
})
})
You have to update your enzyme wrapper inside the timeout.
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it('should trigger _handleChildClick', async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () => new Promise((resolve, reject) => {
resolve(
{
name: "some data"
}
)
})
}))
const mountWrapper = await mount(<App />)
mountWrapper.update()
console.log("mountWrapper", mountWrapper.debug()) // showing the loader one
setTimeout(() => {
//**An update required here
mountWrapper.update();
console.log("mountWrapper", mountWrapper.debug()) // nothing showing
// expect(mountWrapper.find('.toggle').length).toEqual(1)
},0)
})
})
Live example created here: https://codesandbox.io/s/5083l6vmjk