I'm trying to learn a bit more about JS tests.
I've got a basic React component that fetch()'s data when it mounts.
When running the app, the component works as expected and gets the data.
However when testing with Jest, I can see that the call has been made but the promise is always rejected?
I've been following this example to produce the tests below.
Not sure about mocking promises with Jest, any pointers would be a huge help!
Component
import React from 'react';
import './App.scss';
import * as Utils from './Functions';
import Header from './components/Header';
import Loader from './components/Loader';
import Table from './components/Table';
export default class App extends React.Component {
constructor(props) {
super(props);
/*
Initialise state:
# Loading: true
*/
this.state = {
loading: true,
};
}
/*
When component mounts,
# Call function to get data
# Set state with promise response
*/
componentDidMount = () => {
/* Function to grab data
I've created a local express server to get around the cors issue
*/
Utils.initData('http://localhost:8888/mock/all').then(data => {
// Finally set state to reload component with new data
this.setState({
loading: false,
teams: data,
})
})
}
render() {
const { loading, teams } = this.state;
return (
<div id="app">
<Header />
<div className="table">
{loading && (<Loader />)}
{!loading && (<Table data={teams} loading={loading} />)}
</div>
</div>
);
}
}
Functions
export const initData = (dataURL) => {
try {
// Get data using the Fetch API
return fetch(dataURL).then(
response => response.json()
)
// Then sanitize the data
.then(data => sanitizeData(data));
} catch (error) {
console.warn(error);
return error;
}
}
export const sanitizeData = (data) => {
console.log(data)
// Do loads of stuff with the data
}
Test
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow, mount } from 'enzyme';
import App from './App';
import Table from './components/Table';
import Header from './components/Header';
import * as Utils from './Functions';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
describe('App', () => {
it('- Renders the header', () => {
const div = document.createElement('div');
ReactDOM.render(<Header />, div);
ReactDOM.unmountComponentAtNode(div);
});
it('- Renders the table', () => {
const div = document.createElement('div');
ReactDOM.render(<Table />, div);
ReactDOM.unmountComponentAtNode(div);
});
it('- Renders the full app', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
});
describe('Gets data', () => {
it('fetches data from server when server returns a successful response', () => {
const mockSuccessResponse = {};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
jest.spyOn(global, 'fetch').mockImplementation(() => mockFetchPromise); // 4
const wrapper = shallow(<App />);
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith('http://localhost:8888/mock/all');
});
});
Error messages
I don't get any error in the app itself but while the test runs I get:
(node:4082) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'forEach' of undefined
[1] (node:4082) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
[1] (node:4082) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
The forEach mentioned above is from the sanitizeData() function and is there because the data param is {} when testing...
You are returning {} in mockJsonPromise which gets passed on to sanitizeData() add hence the forEach loop is not working. Return a list with mock data instead.
const mockSuccessResponse = {};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
jest.spyOn(global, 'fetch').mockImplementation(() => mockFetchPromise);
According to the above code response.json() will resolve to mockSuccessResponse which is {}
Related
i am getting typeerror here in my transferproduct.js file
const { datas } = await axios.get(
"http://localhost:8080/api/QueryProductById/"+data.id
);
let parseData = JSON.parse(datas.response);
setProduct(parseData);
resposne getting from API with ID 1:
{"response":"{\"id\":\"1\",\"name\":\"Product1\",\"area\":\"gfg\",\"ownerName\":\"gf\",\"cost\":\"1000\"}"}
Error getting in console :
transferproduct.js:38 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'response')
at handleSubmit (transferproduct.js:38:1)
Maybe do something like this. axios.get doesn't return an object with a property called datas
import "./styles.css";
import axios from "axios";
import { useEffect, useState } from "react";
export default function App() {
const [todo, setTodo] = useState<null | []>(null);
const getData = async () => {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/todos/1"
);
setTodo(response.data);
};
useEffect(() => {
getData();
}, []);
return (
<div className="App">
<h1>Data</h1>
{todo && <p>{JSON.stringify(todo)} </p>}
</div>
);
}
I created a codesandbox here so you can test it out
i am trying to make a system with face-api that recognizes a face on the webcam and compares it with that of an image. But when I try to print by console const detections = await faceapi.detectSingleFace(video.current) it returns the following result:
enter image description here
import React from 'react';
import * as faceapi from 'face-api.js';
import {useRef, useEffect} from 'react';
function IdBiometrica(){
const video = useRef()
useEffect(() => {
const getUserMedia = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({video: true});
video.current.srcObject = stream;
} catch (err) {
console.log(err);
}
};
getUserMedia();
}, []);
Promise.all([
faceapi.nets.ssdMobilenetv1.loadFromUri('/models'),
faceapi.nets.tinyFaceDetector.loadFromUri('/models'),
faceapi.nets.faceRecognitionNet.loadFromUri('/models'),
faceapi.nets.faceLandmark68Net.loadFromUri('/models')
]).then(recognition())
function recognition(){
setInterval(async () => {
const detections = await faceapi.detectSingleFace(video.current)
console.log(detections)
}, 10000)
}
return(
<div>
<video ref={video} id='video' width="480" height="240" autoPlay={true} muted></video>
</div>
)
}
export default IdBiometrica
This is an app component created with Create-React-App.
I don't understand why throw an error but after throwing a correct result.
Does anyone have any idea of what the problem is?
Thanks for help!
I was trying to display an array of data fetched from my custom server with RTK Query using Next.js (React framework). And this is my first time using RTK Query. Whenever I console.log the data, it appears in the browser console. But whenever I try to map the data to render it in the browser, it keeps throwing an error saying Cannot read properties of undefined (reading 'map').
I figured Next.js always throws an error if an initial state is undefined or null even if the state change. This link talked about solving the problem using useMemo hook https://redux.js.org/tutorials/essentials/part-7-rtk-query-basics
But I didn't understand it well. Please kindly help me out with displaying the data.
Here is the BaseQuery function example I followed, it was derived from redux toolkit docmentation https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#axios-basequery
import axios from "axios";
const axiosBaseQuery =
({ baseUrl } = { baseUrl: "" }) =>
async ({ url, method, data }) => {
try {
const result = await axios({ url: baseUrl + url, method, data });
return { data: result.data };
} catch (axiosError) {
let err = axiosError;
return {
error: { status: err.response?.status, data: err.response?.data },
};
}
};
export default axiosBaseQuery;
I make the GET request here
import { createApi } from "#reduxjs/toolkit/query/react";
import axiosBaseQuery from "./axiosBaseQuery";
export const getAllCarsApi = createApi({
reducerPath: "getAllCarsApi",
baseQuery: axiosBaseQuery({
baseUrl: "http://localhost:5000/",
}),
endpoints(build) {
return {
getAllCars: build.query({
query: () => ({ url: "all-cars", method: "get" }),
}),
};
},
});
export const { useGetAllCarsQuery } = getAllCarsApi;
This is my redux store
import { configureStore } from "#reduxjs/toolkit";
import { getAllCarsApi } from "./getAllCarsApi";
import { setupListeners } from "#reduxjs/toolkit/dist/query";
const store = configureStore({
reducer: { [getAllCarsApi.reducerPath]: getAllCarsApi.reducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(getAllCarsApi.middleware),
});
setupListeners(store.dispatch);
export default store;
I provide the store to the _app.js file.
import "../styles/globals.css";
import axios from "axios";
import { MyContextProvider } from "#/store/MyContext";
import { Provider } from "react-redux";
import store from "#/store/ReduxStore/index";
axios.defaults.withCredentials = true;
function MyApp({ Component, pageProps }) {
return (
<MyContextProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</MyContextProvider>
);
}
export default MyApp;
I get the data here in my frontend.
import { useGetAllCarsQuery } from "#/store/ReduxStore/getAllCarsApi";
const theTest = () => {
const { data, isLoading, error } = useGetAllCarsQuery();
return (
<div>
{data.map((theData, i) => (
<h1 key={i}>{theData}</h1>
))}
<h1>Hello</h1>
</div>
);
};
export default theTest;
This is a timing thing.
Your component will always render immediately and it will not defer rendering until data is there. That means it will also render before your data has been fetched. So while the data is still loading, data is undefined - and you try to map over that.
You could do things like just checking if data is there to deal with that:
const theTest = () => {
const { data, isLoading, error } = useGetAllCarsQuery();
return (
<div>
{data && data.map((theData, i) => (
<h1 key={i}>{theData}</h1>
))}
<h1>Hello</h1>
</div>
);
};
I am using nuxt with jest to test my axios isit working but i keep having this error which i not sure what happen.
AppNotifications.vue
<template>
<div>
<li v-for="notification in notifications" :key="notification.id">
{{ notification.body }}
</li>
</div>
</template>
<script>
export default {
data() {
return {
notifications: []
}
},
methods: {
async takeNotifications() {
let response = await this.$axios.$get('/notifications.json')
console.log(response)
// this.notifications = response.data.data
}
},
mounted() {
this.takeNotifications()
}
}
</script>
AppNotifications.spec.js
import {
mount
}
from '#vue/test-utils'
import AppNotifications from '#/components/AppNotifications.vue'
import axios from 'axios'
jest.mock('axios', () => {
return {
$get: jest.fn(() => Promise.resolve({
name: 'alex'
}))
}
})
describe('AppNotifications', () => {
it('renders a list of notifications', () => {
let wrapper = mount(AppNotifications)
})
})
Error call out test:
Determining test suites to run...[warn] `mode` option is deprecated. Please use `ssr: true` for universal mode or `ssr: false` for spa mode and remove `mode` from `nuxt.config`
RUNS __tests__/AppNotifications.spec.js
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "TypeError: Cannot read properties of undefined (reading '$get')".] {
code: 'ERR_UNHANDLED_REJECTION'
}
what is wrong with this test ? is my first time doing test on jest.
I am new to React/redux with Node. I am working on a full stack app that utilizes Node.js on the server side and React/Redux on the client side. One of the functions of the app is to provide a current and eight-day weather forecast for the local area. The Weather route is selected from a menu selection on the client side that menu selection corresponds to a server side route that performs an axios.get that reaches out and consumes the weather api (in this case Darksky) and passes back that portion of the JSON api object pertaining to the current weather conditions and the eight-day weather forecast. There is more to the API JSON object but the app consume the "current" and "daily" segment of the total JSON object.
I have written a stand-alone version of the server-side axios "get" that successfully reaches out to the Darksky API and returns the data I am seeking. I am, therefore, reasonably confident my code will correctly bring back the data that I need. My problem consists in this: when I try to render the data in my React Component, the forecast object is undefined. That, of course, means there is nothing to render.
I have reviewed my code, read a plethora of documentation and even walked through tutorials that should help me find the problem and it still eludes me. So, I am stuck and would greatly appreciate some help. Most of the comment you still in the code below will be removed after the debugging process is completed.
I am including code blocks relevant to the problem:
My React Component
// client/src/components/pages/functional/Weather.js
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Moment from 'react-moment';
import Spinner from '../../helpers/Spinner'
import { getWeather } from '../../../redux/actions/weather'
const Weather = ({ getWeather, weather: { forecast, loading } }) => {
// upon load - execute useEffect() only once -- loads forecast into state
useEffect(() => { getWeather(); }, [getWeather])
return (
<div id='page-container'>
<div id='content-wrap' className='Weather'>
{ loading ?
<Spinner /> :
<>
<div className='WeatherHead box mt-3'>
<h4 className='report-head'>Weather Report</h4>
</div>
{/* Current Weather Conditions */}
<h6 className='current-head'>Current Conditions</h6>
<section className='CurrentlyGrid box mt-3'>
/* additional rendering code removed for brevity */
<span><Moment parse='HH:mm'>`${forecast.currently.time}`</Moment></span>
/* additional rendering code removed for brevity */
</section>
</>
}
</div>
</div>
);
};
Weather.propTypes = {
getWeather: PropTypes.func.isRequired,
weather: PropTypes.object.isRequired
};
const mapStateToProps = state => ({ forecast: state.forecast });
export default connect( mapStateToProps, { getWeather } )(Weather);
My React Action Creator
// client/src/redux/actions/weather.js
import axios from 'axios';
import chalk from 'chalk';
// local modules
import {
GET_FORECAST,
FORECAST_ERROR
} from './types';
// Action Creator
export const getWeather = () => async dispatch => {
try {
// get weather forecast
const res = await axios.get(`/api/weather`);
console.log(chalk.yellow('ACTION CREATOR getWeather ', res));
// SUCCESS - set the action -- type = GET_WEATHER & payload = res.data (the forecast)
dispatch({
type: GET_FORECAST,
payload: res.data
});
} catch (err) {
// FAIL - set the action FORECAST_ERROR, no payload to pass
console.log('FORECAST_ERROR ',err)
dispatch({
type: FORECAST_ERROR
});
};
};
My React Reducer
// client/src/redux/reducers/weather.js
import {
GET_FORECAST,
FORECAST_ERROR,
} from '../actions/types'
const initialState = {
forecast: null,
loading: true
}
export default (state = initialState, action) => {
const { type, payload } = action
switch (type) {
case GET_FORECAST:
return {
...state,
forecast: payload,
loading: false
}
case FORECAST_ERROR:
return {
...state,
forecast: null,
loading: false
}
default:
return state
}
}
My Node Route
// server/routes/api/weather.js
const express = require('express');
const axios = require('axios');
const chalk = require('chalk');
const router = express.Router();
// ***** route: GET to /api/weather
router.get('/weather', async (req, res) => {
try {
// build url to weather api
const keys = require('../../../client/src/config/keys');
const baseUrl = keys.darkskyBaseUrl;
const apiKey = keys.darkskyApiKey;
const lat = keys.locationLat;
const lng = keys.locationLng;
const url = `${baseUrl}${apiKey}/${lat},${lng}`;
console.log(chalk.blue('SERVER SIDE ROUTE FORECAST URL ', url));
const res = await axios.get(url);
// forecast -- strip down res, only using currently{} & daily{}
const weather = {
currently: res.data.currently,
daily: res.data.daily.data
};
console.log(chalk.yellow('SERVER SIDE ROUTE FORECAST DATA ', weather));
// return weather
res.json({ weather });
} catch (error) {
console.error(chalk.red('ERR ',error.message));
res.status(500).send('Server Error');
}
});
module.exports = router;
My Express server middleware pertaining to routes (just to be thorough)
// server/index.js
/* code deleted for brevity */
// define routes
app.use('/api/users', require('./routes/api/users'));
app.use('/api/auth', require('./routes/api/auth'));
app.use('/api/weather', require('./routes/api/weather'));
app.use('/api/favorites', require('./routes/api/favorites'));
/* code deleted for brevity */
If the code snippets included are not sufficient, the repo resides here: https://github.com/dhawkinson/TH12-BnBConcierge
Thank you in advance for help with this.
***** Updates *****
I notice that the console logs I have in both actions/weather.js & reducers/weather.js on the client side & routes/api/weather.js on the server side are NOT firing. That tells me that those modules must not be executing. That would explain why I am getting the error "Cannot read property 'currently' of undefined" in client/src/components/pages/functional/Weather.js. Clearly I have a missing link in this chain. I just can't see what it is.
I tried a small refactor, based on input below. I was trying to see if there was some kind of naming conflict going on. this is what I did in my React functional Component:
// client/src/components/pages/functional/Weather.js
...
const mapStateToProps = state => ({weather: { forecast: state.forecast, loading: state.loading }});
...
It didn't help.
I see that in your combineReducers here you are setting as
export default combineReducers({
alert,
auth,
weather
})
So in the store, things gets saved as { alert: {...}, auth: {...}, weather: {...}}. Can you try accessing the forecast value in your Weather as state.weather.forecast ?
const mapStateToProps = state => ({ forecast: state.weather.forecast });
Let me know if it works.
You need to modify your component.
const dispatch = useDispatch();
useEffect(() => { dispatch(getWeather()); }, [getWeather])
And your mapToStateToProps should be as follows:
const mapStateToProps = state => ({ forecast: state.weather.forecast });