Jest wait until typemoq function called - javascript

So I have a mock (typemoq) http call that I'm passing into my react component (mounted with enzyme):
const mockhttp: TypeMoq.IMock<IHttpRequest> = TypeMoq.Mock.ofType<IHttpRequest>();
mockhttp
.setup(x => x.get('/get-all-goal-questions'))
.returns(() => {
return Promise.resolve(mockResponse.object.data);
});
const wrapper = mount(<Goals history={Object} http={mockhttp.object} />);
expect(wrapper.find('#foo')).to.have.lengthOf(1);
However, the mock "Get" isn't being called until after the expected, how can I get the expect to wait until the mock is called to test?
// Edit here is the code under test
let httpCall = this.props.pageHoc.httpRequest -- the call im mocking
import React, { Component } from 'react';
import { Row, Col } from 'react-bootstrap';
import { Animated } from "react-animated-css";
import { Answer, IPageHOC } from '../../interfaces/pageObjects';
// Fonts
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faCheck } from '#fortawesome/free-solid-svg-icons'
// Cookies
import cookie from 'react-cookies';
// Google analytics
import ReactGA from 'react-ga';
type GoalsComponent = {
answers: Answer[],
showError:boolean,
showAnimation:boolean,
question:string,
questionId:number
};
type Props = {
history:any,
pageHoc?: IPageHOC
}
export default class Goals extends Component<Props, GoalsComponent>
{
constructor(props: any) {
super(props);
this.state = {
answers : [],
showError: false,
showAnimation:false,
question: "",
questionId: 0
}
}
componentDidMount(){
// Hide nav
this.props.pageHoc.hideRightNav();
this.loadQuestions();
}
loadQuestions(){
// Setup auth
let auth = this.props.pageHoc.externalAuth;
auth.setToken(cookie.load('Email'), cookie.load('Password')).then((x) => {
let httpCall = this.props.pageHoc.httpRequest;
// Headers
httpCall.setHeaders({
Organization: cookie.load('Organization')
});
httpCall.get(`/thrive/goal/get-all-goal-questions`)
.then((x) => {
this.setState({
answers:x.data.goalQuestions[0].answers,
question: x.data.goalQuestions[0].question,
questionId: x.data.goalQuestions[0].id
});
})
.catch((x) => {
console.log(x, "error");
});
});
}
render() {
return (
<ul className="list-group list-group-goals">
{this.state.answers.map((x:Answer) =>
<li className={("list-group-item ") + (x.selected ? "selected" : "")} key={x.id} onClick={() => this.toggleGoal(x.id)}>
{x.answer}
<FontAwesomeIcon icon={faCheck} className={("goal-tick ") + (x.selected ? "goal-tick-red" : "")} />
</li>
)}
</ul>
);
}
}

hmm if you are trying to test async request you should follow what is written here:
https://jestjs.io/docs/en/tutorial-async
for the short version your test should look something like this:
it('works with async/await', async () => {
expect.assertions(1);
const data = await user.getUserName(4);
expect(data).toEqual('Mark');
});

You can do something like this:
fun test = async () => {
const mockhttp: TypeMoq.IMock<IHttpRequest> = TypeMoq.Mock.ofType<IHttpRequest>();
mockhttp
.setup(x => x.get('/get-all-goal-questions'))
.returns(() => {
return Promise.resolve(mockResponse.object.data);
});
const wrapper = await mount(<Goals history={Object} http={mockhttp.object} />);
expect(wrapper.find('#foo')).to.have.lengthOf(1);
}
This will wait for the promise returned by the mocked get function to resolve and the component to render with the latest data.

Related

How to properly mock `antd` Upload Dragger with jest?

I have the following react code in my project
import React from 'react';
import { Upload } from 'antd';
const { Dragger } = Upload;
...
<Dragger
accept={ACCEPTED_FORMATS}
beforeUpload={beforeUpload}
data-testid="upload-dragger"
maxCount={1}
onChange={({ file: { status } }) => {
if (status === 'done') onUploadComplete();
}}
progress={progress}
showUploadList={false}
>
{/* here i have a button, code ommited for clarity, if needed i'll post it */}
</Dragger>
And I want to test if the callback function onUploadComplete() was called when file.status is 'done'.
Here is how i am doing the test right now:
I have a jest.mock to simulate a dumb request that will always succeed
import React from 'react';
import { fireEvent, render, waitFor } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import { DraggerProps } from 'antd/lib/upload';
import userEvent from '#testing-library/user-event';
import UploadCompanyLogo from '../UploadCompanyLogo'; // This is the component where the dragger is placed
jest.mock('antd', () => {
const antd = jest.requireActual('antd');
const { Upload } = antd;
const { Dragger } = Upload;
const MockedDragger = (props: DraggerProps) => {
console.log('log test');
return (
<Dragger
{...props}
action="greetings"
customRequest={({ onSuccess }) => {
setTimeout(() => {
onSuccess('ok');
}, 0);
}}
/>
);
};
return { ...antd, Upload: { ...Upload, Dragger: MockedDragger } };
});
The test itself (same file as the mock), where it renders the component (where antd will be imported), simulate an upload and then checks if the callback has been called.
it('completes the image upload', async () => {
const flushPromises = () => new Promise(setImmediate);
const { getByTestId } = render(elementRenderer({ onUploadComplete }));
const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
const uploadDragger = await waitFor(() => getByTestId('upload-dragger'));
await act(async () => {
userEvent.upload(uploadDragger, file);
});
await flushPromises();
expect(onUploadComplete).toHaveBeenCalledTimes(1);
expect(onUploadComplete).toHaveBeenCalledWith();
});
elementRenderer
const elementRenderer = ({
onUploadComplete = () => {}
}) => (
<ApplicationProvider>
<UploadCompanyLogo
onUploadComplete={onUploadComplete}
/>
</ApplicationProvider>
);
ApplicationProvider
import React, { PropsWithChildren } from 'react';
import { ConfigProvider as AntdProvider } from 'antd';
import { RendererProvider as FelaProvider } from 'react-fela';
import { createRenderer } from 'fela';
import { I18nextProvider } from 'react-i18next';
import antdExternalContainer, {
EXTERNAL_CONTAINER_ID,
} from 'src/util/antdExternalContainer';
import { antdLocale } from 'src/util/locales';
import rendererConfig from 'src/fela/felaConfig';
import i18n from 'src/i18n';
const ApplicationProvider = (props: PropsWithChildren<{}>) => {
const { children } = props;
return (
<AntdProvider locale={antdLocale} getPopupContainer={antdExternalContainer}>
<FelaProvider renderer={createRenderer(rendererConfig)}>
<I18nextProvider i18n={i18n}>
<div className="antd-local">
<div id={EXTERNAL_CONTAINER_ID} />
{children}
</div>
</I18nextProvider>
</FelaProvider>
</AntdProvider>
);
};
export default ApplicationProvider;
This is currently not working, but have already been improved with the help of #diedu.
The console.log() I have put in the MockedDragger it's currently not showing. If I put a console.log() in both component and mockedDragger, it prints the component log.
Any tips on how to proceed?
I have already seen this issue and didn’t help.
The first thing you should change is the return you are doing in your mock. You are returning a new component called MockedDragger, not Dragger.
return { ...antd, Upload: { ...Upload, Dragger: MockedDragger } };
The next thing is the event firing. According to this issue you should use RTL user-event library and wrap the call in act
import userEvent from "#testing-library/user-event";
...
await act(async () => {
userEvent.upload(uploadDragger, file);
});
...
and finally, due to some asynchronous code running you need to flush the pending promises before checking the call
const flushPromises = () => new Promise(setImmediate);
...
await flushPromises();
expect(onUploadComplete).toHaveBeenCalled()
this is the full version
import { render, waitFor } from "#testing-library/react";
import App from "./App";
import userEvent from "#testing-library/user-event";
import { act } from "react-dom/test-utils";
import { DraggerProps } from "antd/lib/upload";
jest.mock('antd', () => {
const antd = jest.requireActual('antd');
const { Upload } = antd;
const { Dragger } = Upload;
const MockedDragger = (props: DraggerProps) => {
return <Dragger {...props} customRequest={({ onSuccess }) => {
setTimeout(() => {
onSuccess('ok');
}, 0)
}} />;
};
return { ...antd, Upload: { ...Upload, Dragger: MockedDragger } };
});
it("completes the image upload", async () => {
const onUploadComplete = jest.fn();
const flushPromises = () => new Promise(setImmediate);
const { getByTestId } = render(
<App onUploadComplete={onUploadComplete} />
);
const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
const uploadDragger = await waitFor(() => getByTestId("upload-dragger"));
await act(async () => {
userEvent.upload(uploadDragger, file);
});
await flushPromises();
expect(onUploadComplete).toHaveBeenCalled();
});
Unfortunately, I couldn't set up a codesandbox to show it's working, but let me know if you face any issue and I can push the code to a repo.

How to write test case for a component which is calling a method using jest

I am new to Jest and Enzyme & facing an issue. Can someone help?
Here is my jsx file (myTemplate.jsx) :
export default (props) => {
const oneSection = () => {
return <div>Hello</div>
};
return (
props.hasData ? { <div>Hey</div> } : { <div><oneSection/></div> }
)
}
And this is my test file :
import React from "react";
import { shallow } from "enzyme";
import myTemplate from '../myTemplate';
describe("template component", () => {
const props= {
hasData: false,
}
let myComponent = null;
beforeAll(() => {
myComponent = shallow(<myTemplate {...props}/>);
})
test("should render with initial state properly", () => {
expect(myComponent).toMatchSnapshot();
})
})
Now this test case is running successfully. Snapshot is getting created with a div which has oneSection but oneSection is not getting replaced with actual html within it. Basically these lines are not getting covered :
const oneSection = () => {
return <div>Hello</div>
};
How can i cover this piece of code using Jest and enzyme ?
import React from 'react'
import { cleanup, render } from '#testing-library/react'
describe('DMReports', () => {
afterEach(cleanup)
test('DMReports: hasData true', () => {
const { container } = render(
<DMReports hasData={true}/>,
)
expect(container).toMatchSnapshot()
})
test('DMReports: hasData false', () => {
const { container, getByText } = render(
<DMReports hasData={false}/>,
)
expect(container).toMatchSnapshot()
expect(getByText('Hello')).toBeTruthy()
})
})

Server Error Error: Invalid hook call. Hooks can only be called inside of the body of a function component in _app.js

I am a newbie in React and Next JS, I want to set initial auth user data on initial load from the __app.js. But using dispatch throwing error "Invalid hook call". I know according to docs calling hooks in render function is wrong. but I am looking for an alternate solution to this.
How I can set auth data one-time so that will be available for all the pages and components.
I am including my code below.
/contexts/app.js
import { useReducer, useContext, createContext } from 'react'
const AppStateContext = createContext()
const AppDispatchContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case 'SET_AUTH': {
return state = action.payload
}
default: {
throw new Error(`Unknown action: ${action.type}`)
}
}
}
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {})
return (
<AppDispatchContext.Provider value={dispatch}>
<AppStateContext.Provider value={state}>
{children}
</AppStateContext.Provider>
</AppDispatchContext.Provider>
)
}
export const useAuth = () => useContext(AppStateContext)
export const useDispatchAuth = () => useContext(AppDispatchContext)
/_app.js
import 'bootstrap/dist/css/bootstrap.min.css'
import '../styles/globals.css'
import App from 'next/app'
import Layout from '../components/Layout'
import { mutate } from 'swr'
import { getUser } from '../requests/userApi'
import { AppProvider, useDispatchAuth } from '../contexts/app'
class MyApp extends App {
render() {
const dispatchAuth = useDispatchAuth()
const { Component, pageProps, props } = this.props
// Set initial user data
const setInitialUserData = async () => {
if (props.isServer) {
const initialData = {
loading: false,
loggedIn: (props.user) ? true : false,
user: props.user
}
const auth = await mutate('api-user', initialData, false)
dispatchAuth({
type: 'SET_AUTH',
payload: auth
})
}
}
//----------------------
// Set initial user data
setInitialUserData()
//----------------------
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}
}
MyApp.getInitialProps = async (appContext) => {
let isServer = (appContext.ctx.req) ? true : false
let user = null
let userTypes = {}
// Get user server side
if (isServer) {
await getUser()
.then(response => {
let data = response.data
if (data.status == true) {
// Set user
user = data.data.user
userTypes = data.data.user_types
//---------
}
})
.catch(error => {
//
})
}
//---------------------
return {
props: {
user,
userTypes,
isServer
}
}
}
export default MyApp
I believe this is the intended use of the useEffect hook with an empty array as its second argument:
https://reactjs.org/docs/hooks-effect.html
import {useEffect} from 'react'
class MyApp extends App {
useEffect(()=> {
setInitialUserData()
},[])
render() {
...
}
}

React Redux not dispatching API Call to delete player

I am trying to migrate my previously working local state to redux. Now loading available Players works just fine, but deleting will somehow stop in the playerActions.js file, where I dispatch and then return an API Call. So to further give details here are my code parts in relevance:
PlayerPage.js (Component):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadPlayers, deletePlayer } from '../../redux/actions/playerActions';
import PlayerForm from './playerform';
import PlayCard from './playercard';
import PropTypes from 'prop-types';
import { toast } from 'react-toastify';
class PlayerPage extends Component {
constructor(props) {
super(props);
this.handleDeletePlayer = this.handleDeletePlayer.bind(this);
state = {};
componentDidMount() {
const players = this.props;
players.loadPlayers().catch(err => {
alert('Loading players failed. ' + err);
});
}
handleDeletePlayer = player => {
toast.success('Player deleted');
try {
deletePlayer(player);
} catch (err) {
toast.error('Delete failed. ' + err.message, { autoClose: false });
}
};
render() {
const styles = {
margin: '20px'
};
return (
<div className="container-fluid">
<div>
<h2 style={styles}>Add Player</h2>
<div className="container-fluid">
<PlayerForm handleAddNewPlayer={this.handleAddPlayer} />
</div>
</div>
<hr></hr>
<div>
<h2 style={styles}>Available Player</h2>
<div className="container-fluid">
{this.props.players.map(player => (
<PlayCard
player={player}
key={player.id}
imageSource={`${process.env.API_URL}/${player.profileImg}`}
onDeletePlayer={this.handleDeletePlayer}
/>
))}
</div>
</div>
</div>
);
}
}
PlayerPage.propTypes = {
players: PropTypes.array.isRequired
};
function mapStateToProps(state) {
return {
players: state.players
};
}
const mapDispatchToProps = {
loadPlayers,
deletePlayer
};
export default connect(mapStateToProps, mapDispatchToProps)(PlayerPage);
And the Action being called is in here:
playerActions.js:
import * as types from './actionTypes';
import * as playerApi from '../../api/playerApi';
export function loadPlayersSuccess(players) {
return { type: types.LOAD_PLAYERS_SUCCESS, players };
}
export function deletePlayerOptimistic(player) {
return { type: types.DELETE_PLAYER_OPTIMISTIC, player };
}
export function loadPlayers() {
return function(dispatch) {
return playerApi
.getAllPlayers()
.then(players => {
dispatch(loadPlayersSuccess(players));
})
.catch(err => {
throw err;
});
};
}
export function deletePlayer(player) {
console.log('Hitting deletePlayer function in playerActions');
return function(dispatch) {
dispatch(deletePlayerOptimistic(player));
return playerApi.deletePlayer(player);
};
}
The console.log is the last thing the app is hitting. But the API Call is never made though.
API Call would be:
playerApi.js:
import { handleResponse, handleError } from './apiUtils';
const axios = require('axios');
export function getAllPlayers() {
return (
axios
.get(`${process.env.API_URL}/player`)
.then(handleResponse)
.catch(handleError)
);
}
export function deletePlayer(id) {
return (
axios
.delete(`${process.env.API_URL}/player/${id}`)
.then(handleResponse)
.catch(handleError)
);
}
I was like spraying out console.log in different places and files and the last one I am hitting is the one in playerActions.js. But after hitting it the part with return function(dispatch) {} will not be executed.
So if someone could point me in a general direction I'd be more than grateful.
It looks like you are calling your action creator deletePlayer but you aren't dispatching it correctly. This is why the console.log is being called but not the method that does the request.
I'd recommend taking a look at the documentation for mapDispatchToProps to fully understand how this works. In your example, you should just need to change the call to deletePlayer in your PlayerPage component to this.props.deletePlayer() to use the action creator after it's been bound to dispatch properly.
this how the mapDispatchToProps should be:
const mapDispatchToProps = dispatch => {
return {
load: () => dispatch(loadPlayers()),
delete: () => dispatch(deletePlayer()),
}
}
then call load players with this.props.load() and delete player with this.props.delete()

ReactJS: Can I separate a function that changes state in a separate component?

I've got a function in my React app that is calling in componentWillMount lifecycle method.
It grabs data from JSON file and push it to the component's state (it is a text data, I later insert that text into the page).
I'm going to use the same function on many other components, can I separate this function into a separate component to make it reusable?
Here is my code:
import React from 'react';
import axios from 'axios';
import logo from '../img/company_logo.png';
import '../css/header.scss';
import getTextData from './getTextData';
const NumberList = (props) => {
console.log(props.value);
const itemList = props.value;
const listItems = itemList.map(number => (
<li key={number.toString()}>
{number}
</li>
));
return (
<ul>{listItems}</ul>
);
};
export default class Header extends React.Component {
constructor() {
super();
this.state = {};
}
componentWillMount() {
axios.get('./data.json')
.then((res) => {
this.setState({
siteData: res.data,
});
})
.catch((err) => {
console.log(err);
});
}
render() {
// console.log(this.state);
const { siteData } = this.state;
if (siteData) {
console.log(siteData.data.mainPage.navBar);
} else {
return null;
}
return (
<div className="headerWrapper">
<img src={logo} alt="company_logo" id="companyLogo" />
<NumberList value={siteData.data.mainPage.navBar} />
</div>
);
}
}
Yes, create a function and return the repsonse-data, must use async await
//fetchService.js
import axios from 'axios';
export default async function fetchService(){
let responseData = [];
await axios.get('./data.json')
.then((res) => {
responseData = res.data;
})
.catch((err) => {
console.log(err);
});
return responseData;
}
// App.js
import fetchService from './fetchService';
async componentDidMount() {
let tempData = await fetchService();
this.setState({
siteData: tempData,
});
}
i hope this helps!

Categories