I have a following useQuery hook usage:
const { data: user, refetch } = useQuery({
queryKey: ['user', userId],
queryFn: () =>
UserApi.getUserById(userId)
.then(({ data }) => data)
.catch(() => {
NotificationManager.error('Error with getting user with id: ' + userId, { icon: true });
})
});
This works fine in my component, but I have problems with tests:
import React from 'react';
import { screen } from '#testing-library/react';
import '#testing-library/jest-dom';
import { UserApi } from '../../api';
import { render } from '../../config/test/root-render';
import useCurrentUserInfo from '../../hooks/useCurrentUserInfo';
import UserDetails from './UserDetails';
jest.mock('../../api/UserApi');
jest.mock('../../hooks/useCurrentUserInfo');
jest.mock('../../root/router', () => {
return {
useCurrentRoute: () => ({ params: { userId: 'userId' }, parent: { key: '' } }),
generateUrlByKey: () => '1'
};
});
function mockApi(userFiles, userStatus, userSecondaryStatus) {
UserApi.getUserById = jest.fn().mockReturnValue(
new Promise((resolve) =>
resolve({
data: { userFiles: userFiles, general: { status: userSecondaryStatus}, userStatus: userStatus}
})
)
);
}
function mockAndRender(userFiles, userStatus, userSecondaryStatus) {
useCurrentUserInfo.mockReturnValue([{}]);
mockApi(userFiles, userStatus, userSecondaryStatus);
render(<UserDetails />);
}
describe('<UserDetails />', () => {
it('test', () => {
mockAndRender([1], 'Awarded', null)
})
})
And as you can see I didn't mock useQuery hook, cause I don't need it. I have to mock api call instead of hook. Moreover, mock of my api call works as expected (I've checked and verified it with debugger), but useQuery is returning undefined. Does anybody has ideas how to fix it?
So, after deep research, I've found the root of the problem. The code described above is correct. But the problem was in lines I did not provide in my question.
expect(screen.getByTestId('username')).toBeInTheDocument();
Method getByTestId is getting data faster than data is defined. After changing the code to this it works as expected:
expect(await screen.findByTestId('username')).toBeInTheDocument();
Related
My api.ts file
import axios from 'axios'
export const api = axios.create({
baseURL: 'http://localhost:3333/',
})
My react-page.spec.tsx file:
import React from 'react'
import '#testing-library/jest-dom'
import { act, cleanup, render, RenderResult } from '#testing-library/react'
import { Companies } from '../../../../modules/companies/pages/companies'
import { Company } from '../../../../#types/company.type'
let companiesPageElement: RenderResult
const companies: Company[] = [
{
_eq: 'id-company-1',
logo: 'https://s.yimg.com/ny/api/res/1.2/a19vkjSUoD4hVV0djOpSLw--/YXBwaWQ9aGlnaGxhbmRlcjt3PTEyMDA7aD02Nzc-/https://s.yimg.com/os/creatr-uploaded-images/2020-07/20e95610-d02d-11ea-9f0c-81042fd4c51a',
name: 'Casas Bahia',
manager: 'Daniel Ribeiro',
status: 'late'
}
]
const mockedAxiosGet = (...args: any) => jest.fn(...args).mockReturnValue({ data: companies })
jest.mock('../../../../api', () => {
return {
api: {
get: (...args: any) => {
return mockedAxiosGet(...args)()
}
}
}
})
describe('<Companies />', () => {
beforeEach(async () => {
await act(async () => {
companiesPageElement = render(<Companies />)
})
})
it('should be able to render correctly the page', async () => {
// It's a test that use the mockedAxiosGet as all other tests
})
it('should be able to go to Create first company page if no companies has found in database', async () => {
// It's the test that I want to change the api.get return value
})
afterEach(() => {
cleanup()
})
})
Need to
I want that when I test my SECOND test, the jest renders the Companies page with a new value. Actually, the value is a empty array ([]).
I tried to rerender component inside the it function with a new api mock.
I Want to implement a specific result to my second test.
I solved my problem with jest-mock-axios
I've been struggling with this one for 2-3 days and hope someone can help. I am moving a blog project over from React to Next, and in one particular case a setState function isn't working.
The code below lives in the _app.tsx function at the top of my project. The editPost function is called from a button in a child component. The code pulls the selected blog post from the database then updates the state of postToEdit. This data is meant to be injected into an edit form via props-- and works fine in the React version of the blog.
In this case, the setState (setPostToEdit) function seems to do nothing. In the console.log function after setPostToEdit(newPostToEdit), you can see that the data has been pulled from Postgres correctly, but the state doesn't change.
In the deletePost and getPosts function in this same _app component, everything works fine. Weird! Any help sincerely appreciated, I'm new to both React and Next.
import '../styles/globals.css'
import React, { useState, useEffect } from 'react'
import type { AppProps } from 'next/app'
import Layout from '../components/Layout'
export default function App({ Component, pageProps }: AppProps) {
const initPostToEdit = {
post_id: '',
title: 'initial post title',
sub_title: '',
main_content: '',
post_url: 'initial URL',
page_title: '',
meta_description: '',
meta_keywords: ''
}
const [posts, setPosts] = useState([]);
const [postToEdit, setPostToEdit] = useState(initPostToEdit);
const [blogValues, setBlogValues] = useState(initPostToEdit);
const deletePost = async (id) => {
try {
await fetch(`http://localhost:5001/blog-edit/${id}`, {
method: "DELETE"
})
setPosts(posts.filter(post => post.post_id !== id))
} catch (error) {
console.error(error.message)
}
}
const editPost = async (id) => {
try {
const response = await fetch(`http://localhost:5001/blog-edit/${id}`, {
method: "GET"
})
const newPostToEdit = await response.json()
setPostToEdit(newPostToEdit)
console.log('postToEdit:', newPostToEdit[0], postToEdit)
window.location.assign("/admin/blog-edit");
} catch (error) {
console.error(error.message)
}
}
const getPosts = async () => {
try {
const response = await fetch("http://localhost:5001/blog-edit");
const jsonData = await response.json();
setPosts(jsonData);
} catch (error) {
console.error(error.message)
}
}
useEffect(() => { getPosts(); }, [])
return (
<div>
<Layout>
<Component
{...pageProps}
editPost={editPost}
postToEdit={postToEdit}
setPostToEdit={setPostToEdit}
blogValues={blogValues}
setBlogValues={setBlogValues}
posts={posts}
deletePost={deletePost}
/>
</Layout>
</div>
)
}
I'm fairly new to react-testing-library and generally testing. I want to test a component that fetches the data from an API in useEffect hook. Then it stores it in local state. It renders these array data with array.map, but i'm getting Error: Uncaught [TypeError: Cannot read properties of undefined (reading 'map')] error. I'm probably doing wrong in my test suite, i've researched a lot but couldn't fix it.
import React from 'react';
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom'
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { OnePiece } from '.';
const server = setupServer(rest.get('server http address', (req, res, ctx) => {
const totalData = [
{ name: "doffy", price: 100, image: "image url" },
{ name: "lamingo", price: 500, image: "image url" }
];
return res(
ctx.status(200),
ctx.json({
data: { crew: totalData }
})
)
}))
beforeAll(() => server.listen());
afterAll(() => server.close());
beforeEach(() => server.restoreHandlers());
//console.log("mocking axios", axios)
describe('OnePiece', () => {
test('fetches the data from the API and correctly renders it', async () => {
//Here's probably where i fail. Please someone tell me the right way :)
await render(<OnePiece />)
const items = await screen.findAllByAltText('product-image');
expect(items).toHaveLength(2);
// screen.debug()
})
})
And the below is the parts of code useEffect, and totalData.map in the component:
const [totalData, setTotalData] = useState([]);
const [crew, setCrew] = useState('straw-hat-pirates');
useEffect(() => {
let isApiSubscribed = true;
const getOpData = async () => {
const getCrews = await axios.get('http address');
if (isApiSubscribed) {
let data = getCrews.data;
data = data[crew];
// console.log("data", data);
setTotalData(data);
}
}
getOpData();
return () => {
isApiSubscribed=false;
}
}, [crew])
.........
//in the return part
<ProductsWrapper>
{totalData.map((product, index) =>
<ProductCard key={index} name={product.name} price={product.price} imageUrl={product.image} />
)}
</ProductsWrapper>
As i predicted, the problem was the async data fetching. Currently setTimeOut is more than enough for me, but if someone sees this in the future, you can look for the waitFor method of react-testing-library.
Here's the fixed part:
describe('OnePiece', () => {
test('fetches the data from the API and correctly renders it', async () => {
render(<OnePiece />)
setTimeout(async () => {
const items = await screen.findAllByAltText('product-image');
expect(items).toHaveLength(2);
}, 4000)
//screen.debug()
})
})
I have a CrudActions.js class:
export default class CrudActions {
constructor(entity, api) {
this.setEntity(entity);
this.setApi(api);
}
setEntity(entity) {
this.entity = entity.toUpperCase();
}
setApi(api) {
this.api = api;
};
getEntity() {
return this.entity;
};
getApi() {
return this.api;
};
fetchItems() {
return dispatch => {
dispatch(
{
type: `TRY_FETCH_${this.getEntity()}_ITEMS`,
}
);
this.getApi()
.fetchItems()
.then(data => {
dispatch({
type: `FETCH_${this.getEntity()}_ITEMS_SUCCEEDED`,
data
});
})
.catch(error => {
dispatch({
type: `FETCH_${this.getEntity()}_ITEMS_FAILED`,
error,
});
})
}
};
}
I extend it with a new class (one class for every route)
import { instance as api } from "../../api/app/Ping";
import CrudActions from "../base/CrudActions";
export default class PingActions extends CrudActions {
constructor() {
super("ping", api);
}
}
export const actions = new PingActions();
I want put under test fetchItems and test that right actions are dispatched.
So, in a Ping.test.js:
import { actions as pingActions } from "../../../../utils/actions/app/PingActions";
import { axiosInstance } from "../../../../utils/api/base/axiosInstance";
import MockAdapter from "axios-mock-adapter";
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const entity = 'ping';
const baseUrl = '/ping';
const dataFetchItems = [
{
app_version: "9.8.7"
}
];
describe('Test PingActions', () => {
let mock;
let store;
beforeEach(() => {
store = mockStore({
ping: {
items: dataFetchItems
}
})
})
beforeAll(() => {
mock = new MockAdapter(axiosInstance);
});
afterEach(() => {
mock.reset();
});
it ('Test can dispatch success actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
store.dispatch(pingActions.fetchItems());
console.log(store.getActions());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS",
});
});
it ('Test can dispatch fail actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(401);
store.dispatch(pingActions.fetchItems());
console.log(store.getActions());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS",
});
});
});
With these tests I can cover both case: "TRY_FETCH_PING_ITEMS" and "FETCH_PING_ITEMS_SUCCEEDED" (I see it from coverage).
I cannot understand how get FETCH_PING_ITEMS_SUCCEEDED or FETCH_PING_ITEMS_FAILED actions in store.getActions().
store.getActions() has only TRY_FETCH_PING_ITEMS inside:
PASS src/__tests__/utils/actions/app/PingActions.test.js
● Console
console.log
[ { type: 'TRY_FETCH_PING_ITEMS' } ]
at Object.<anonymous> (src/__tests__/utils/actions/app/PingActions.test.js:46:13)
console.log
[ { type: 'TRY_FETCH_PING_ITEMS' } ]
at Object.<anonymous> (src/__tests__/utils/actions/app/PingActions.test.js:55:13)
I made a new test, without luck:
it ('Test can dispatch success actions', async () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
await store.dispatch(pingActions.fetchItems());
console.log(store.getActions());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS",
});
});
But I get...
PASS src/__tests__/utils/actions/app/PingActions.test.js
● Console
console.log
[ { type: 'TRY_FETCH_PING_ITEMS' } ]
at Object.<anonymous> (src/__tests__/utils/actions/app/PingActions.test.js:46:13)
(I miss, every time, the FETCH_PING_ITEMS_SUCCEEDED)
Another test:
it ('Test can dispatch success actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
return store.dispatch(pingActions.fetchItems()).then(data => console.log(data));
});
But I get
TypeError: Cannot read property 'then' of undefined
Or also:
it ('Test can dispatch success actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
const data = pingActions.fetchItems().then(data => console.log(data));
});
I get
TypeError: _PingActions.actions.fetchItems(...).then is not a function
The Github Repository: https://github.com/sineverba/body-measurement-frontend
A few bit changes will make it work.
The Problem
You expect that FETCH_PING_ITEMS_SUCCEEDED or FETCH_PING_ITEMS_FAILED actions should be dispatched after the TRY_FETCH_PING_ITEMS action. since both success and failure cases are a promise, so they need to be processed in the proper way (nicely implemented in the CrudActions with then/catch block) but you need to handle these asynchronous actions also in your test case after dispatching the TRY_FETCH_PING_ITEMS.
The Solution
from React testing library documentation:
When in need to wait for any period of time you can use waitFor, to wait for your expectations to pass.
import {waitFor} from '#testing-library/react'
it('Test can dispatch success actions', async () => {
mock.onGet('http://localhost:8000/api/v1' + baseUrl).reply(200);
store.dispatch(pingActions.fetchItems());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS"
})
await waitFor(() => {
expect(store.getActions()).toContainEqual({
type: "FETCH_PING_ITEMS_SUCCEEDED",
})
})
})
You can also put the fetch ping expectation in the waitFor callback.
await waitFor(() => {
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS"
})
expect(store.getActions()).toContainEqual({
type: "FETCH_PING_ITEMS_SUCCEEDED",
})
})
Note: Don't forget to add async keyword before the callback function in the it method.
Note: For failure case, do the as same as the success case.
Here's a generic example of testing a thunk, hope this helps.
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import getApiClient from './api-client';
import { initialState } from './reducer';
import * as Actions from './actions';
jest.mock('api-client');
jest.mock('actions');
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('getStuff async action', () => {
it('should request data via API', async () => {
const store = mockStore({ stuff: initialState });
// Mock api client method so we're not sending out actual http requests
getApiClient.mockImplementationOnce(() => ({
getStuff: async () => [{ id: '1' }],
}));
// Don't remember if or why this part was necessary, but it was working regardless :D
const Actions = require('./actions');
// Describe the expected sequence of actions dispatched from the thunk
const expected = [
{ type: 'STUFF/REQUEST' },
{
type: 'STUFF/SUCCESS',
payload: { items: [{ id: '1' }] },
},
];
// Dispatch the thunk and wait for it to complete
await store.dispatch(Actions.getStuff('1'));
const dispatchedActions = store.getActions();
expect(dispatchedActions[0]).toEqual(expected[0]);
expect(dispatchedActions[1].payload).toEqual(expect.objectContaining(expected[1].payload));
});
});
I have some code which works. However for my test I would like to mock the fetch that is done in the component.
The test
I am trying the following:
import ConnectedComponent from './Component';
import { render } from '#testing-library/react';
import user from '../__models__/user'; // arbitrary file for the response
// create a mock response
const mockSuccessResponse = user;
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
// Trying mock the refetch from http
jest.mock('./http', () => {
return {
refetch: () => ({
settingsFetch: () => mockFetchPromise,
})
}
});
it('renders', async () => {
const { getByText } = render(Component);
const title = await getByText('My title');
expect(title).toBeInTheDocument();
});
Error message
With this I receive the following error:
● Test suite failed to run
TypeError: (0 , _http.refetch)(...) is not a function
The Application code
This code is working fine in my application. To give you an example:
./http.js
import { connect } from 'react-refetch';
export async function fetchWithToken(urlOrRequest, options = {}) {
// some stuff
return response;
}
export const refetch = connect.defaults({
fetch: fetchWithToken,
});
./Component.jsx
import { refetch } from './http';
const Component = ({ settingsFetch }) => <AnotherComponent settingsFetch={settingsFetch} />);
const ConnectedComponent = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'http://some-url/api/v1/foo'
}
})
)(Component)
export default ConnectedComponent;
How can I mock this function to return a mocked Promise as the response?
Update: It's getting close by doing the following:
jest.mock('../helpers/http', () => ({
refetch: () => jest.fn(
(ReactComponent) => (ReactComponent),
),
}));
Now the error reads:
Warning: Failed prop type: The prop `settingsFetch` is marked as required in `ConnectedComponent`, but its value is `undefined`.
Which means I will probably have to provide the mocked responses for the fetches in there somewhere.
Jest itself is in charge of the modules. So in the following example you will see that the module coming from '../http' can be mocked.
You can then overwrite the props of that module by first adding the default props, and after that overwrite the ones you need with your own.
jest.mock('../http', () => {
return {
refetch: function(hocConf) {
return function(component) {
component.defaultProps = {
...component.defaultProps,
settingsFetch: {},
// remember to add a Promise instead of an empty object here
};
return component;
};
},
};
});