Jest test mock -- mock fetch inside another function - reactjs

I have a stateful component I'm trying to test. This component serves as an intermediate step before it calls my other function. Kinda works like this
class MyList extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
this.props.loadMystuff().then(() => {
if (this.state.eKey !== this.props.eKey) {
let eKey = this.props.eKey;
this.fetchSList(eKey);
}
}).catch((error) => toast(error.message));
}
fetchSList(eKey) {
if (eKey !== '') {
fetch(`some_api_url_config/${this.props.entityKey}`)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
}
}
render() {
const { error, isLoaded, items } = this.state;
if (items) {
return (
<div>
<h3>S List</h3>
<ul>
{items.map(item => (
<li key={item}>
{item}
</li>
))}
</ul>
</div>
);
} else if (error) {
return <div>Error: List Missing...{error.message}</div>;
} else if (!isLoaded) {
return <div>Loading List...</div>;
} else {
return <div>Not Found</div>;
}
}
}
the loadMystuff essentially gives me an eKey which I can use to call fetchSList, which calls fetch
I want to mock fetch and make it return an array of strings, but I haven't had any luck achieving that.
My test script looks like
describe(<MyList />, () => {
let wrapper;
let eKey = 'some_str';
it("should have some listed items", () => {
wrapper = shallow(<MyList loadMystuff={loadMystuff()}
eKey={eKey}/>
);
expect(wrapper.find("div").find("ul").find("li").length).toBeGreaterThan(0);
})
)
How do I make the fetch command return an array like ['abc', 'def', 'ghi']?
EDIT:
after reading https://medium.com/#ferrannp/unit-testing-with-jest-redux-async-actions-fetch-9054ca28cdcd
I have come up with
it("should have some listed items", () => {
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, '["abc", "def"]')));
return store.dispatch(MyList(loadMystuff=loadMystuff(), eKey=eKey))
.then(() => {
const expectedActions = store.getActions();
console.log('expectedActions', expectedActions);
});
})
but this doesn't seem to work
EDIT2:
I'm now investigating fetch-mock package.
My function is still fetchSList where there's a fetch in it. I'm running the test as
let eKey = 'some_key';
describe(<MyList />, () => {
fetchMock.get('*', '["abc", "def"]');
it("should have some listed items", () => {
wrapper = shallow(<MyList loadMyStuff={loadMyStuff()}
eKey={eKey}/>
);
expect(wrapper.find("div").find("ul").find("li")).toBe('something_something');
and it's returning an object instead of some form of string. jest is nice enough to print out what's in the object and there's nothing I expect, which is ["abc", "def"]

You can mock the responses for HTTP requests with a library such as nock or fetch-mock.

Related

How to find all elements with text react-test-renderer

I have a component that makes a GET request and sends users their information. I need to test whether there is such an "li" with a certain text using a test. Now in my code the error is "promise returned from findByType query must be handled". How can I solve it?
describe("List Component", () => {
it("find li", () => {
const wrapper = renderer.create(<List />);
const testInstance = wrapper.root;
expect(testInstance.findByType("li")).toBeTruthy();
});
});
class List extends Component {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
getUser(this.props.user)
.then((response) => {
this.setState(response.data);
})
.catch((error) => {
this.setState({ error: "request error" });
});
}
render() {
return (
<div>
<ul>
{Object.keys(this.state).map((i) => (
<li key={i}>
{i}: {this.state[i]}
</li>
))}
</ul>
</div>
);
}
}

How I do use fetch API and store response in the state?

I have to get a file from the server, After the component is rendered, that contains information from cities, and I must assign it to "citiesData" in the state. But the data is not received because it is not seen in the output.
what is the issue with my fetch?
server file:
IranMap(the file seems to have a problem in fetch):
import React from 'react';
import './IranMap.css';
import CityModal from './CityModal';
class IranMap extends React.Component {
state = {
error: null,
citiesData: null,
selectedCity: null,
isModalOpen: false,
};
componentDidMount() {
fetch('http://localhost:9000/cities')
.then(response => response.json())
.then((result) => {
this.setState({
citiesData: result
});
},
(error) => {
this.setState({
error
});
}
)
}
cityClicked = (id) => (event) => {
event.preventDefault();
fetch(`http://localhost:9000/cities/${id}`,{method: 'GET'})
.then(res => res.json())
.then(result => {
this.setState({
selectedCity: result,
isModalOpen: true
});
})
}
closeModal = () => {
this.setState({
isModalOpen: false,
});
};
render() {
if(this.state.error){
return <div>Error: {this.state.error.message}</div>;
}
else {
return (
<div>
<div className="map-container">
{(this.state.citiesData || []).map((record) => (
<div
key={record.id}
className="city-name"
style={{
top: `${record.top}%`,
left: `${record.left}%`,
}}
onClick={this.cityClicked(record.id)}
>
{record.name}
</div>
))}
</div>
<CityModal
city={this.state.selectedCity}
isOpen={this.state.isModalOpen}
onClose={this.closeModal}
/>
</div>
);
}
}
}
export default IranMap;
This is my output. it should be show cities name. but this is empty:
I think what you are trying to do is render the entire object,u cant do that, try the render each element, The second part of my answer is that you should use an asynchronous task.
I hope my answer guided you

How to setState to answer from APi and use map

Im trying to create recipes searcher. In App.js I receive query from search input from another component and I want to setState to answer from APi. Console.log from callback in setState shows updated state but the state is not updated. I need setState updaed so I can use map on it and display list of recipes in render. It gives me error map is not a function because this.state.recipesList is still empty. Anyone can help me ?
class App extends Component {
state = {
query: "",
recipesList: []
};
getQuery = query => {
const key = "2889f0d3f51281eea62fa6726e16991e";
const URL = `https://www.food2fork.com/api/search?key=${key}&q=${query}`;
fetch(URL)
.then(res => res.json())
.then(res => {
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
});
console.log(this.state.recipesList);
};
render() {
const test = this.state.recipesList.map(item => {
return (
<div className="recispesList">
<h1>{item.title}</h1>
</div>
);
});
return (
<div className="App">
<Search query={this.getQuery} />
<div className="contentWrapper">{}</div>
</div>
);
}
}
Search component:
class Search extends Component {
state = {
searchValue: ""
};
handleChange = val => {
let searchValue = val.target.value;
this.setState({
searchValue
});
};
handleSubmit = e => {
e.preventDefault();
this.setState({
searchValue: ""
});
this.props.query(this.state.searchValue);
};
render() {
return (
<div className="searchWrapper">
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.searchValue} />
<button />
</form>
</div>
);
}
}
export default Search;
It seems that instead of directly assigning the whole response to recipesList:
this.setState(
{
recipesList: res
},
() => {
console.log(this.state.recipesList);
}
);
you need to get recipes array first via res.recipes:
this.setState(
{
recipesList: res.recipes
},
() => {
console.log(this.state.recipesList);
}
);

reactjs -- this.props being empty in componentDidMount

I've seen many posts regarding this.props, but none of them seems to answer my question, or at least I couldn't find it.
My component uses this.props as args to get my url
This following code is almost the exact copy of https://reactjs.org/docs/faq-ajax.html
import React, { Component } from 'react';
class MyList extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
let entityKey = this.props.eKey;
fetch(`some/url/${this.props.eKey}`)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading My List...</div>;
} else {
if (!items) {
return <div>Failed to Load My List</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item}>
{item}
</li>
))}
</ul>
);
}
}
}
}
export default MyList
Now my script that calls this is simply
class MyFunc extends Component {
return (
<div>
<MyList
eKey={this.props.eKey}
/>
</dev>
);
}
I eliminated other code in MyFunc for simplicity
When I console.log my this.props inside MyList, it returns {eKey: ""}, so I know that the eKey is being passed in. However, as you can see, it's empty.
I don't know if it's because I have this at the componentDidMount cycle which for some reason this.props hasn't arrived when fetching. If that's the case, how do I guarantee the arrival before executing that line?
If it's somewhere else where I have issue, what went wrong?
EDIT:
To add in some more info. This is tied to a reducer. The redux is not exactly my strong suite.....so please correct me on any concept that's wrong.
The reducer takes the initialState and an action. The action can be different things. The only place that eKey is loaded is when action.type='load'
export function reducer(state = initialState, action) {
switch (action.type) {
case LOAD:
return {
...state,
eKey: action.data.eKey,
// and some other stuff
};
// some other cases here
default:
return state;
}
}
Interesting thing is there's a submit button that updates another component, which supposedly get a new eKey and get the eKey's data. At that time, the eKey is always populated, but my myList is not updated accordingly. I think it's another issue I have to figure out, but just wanna put it out here in case it's somehow related.
It seems like the reducer is not called only the component is mounted and componentDidMount is already called. And MyList does not update when the reducer is finally called. I'm not sure how to solve this problem
EDIT 2:
I tried moving what's inside componentDidMount into the render function (with some mod, of course). Does it work? sure, but when I console.log something, it just continues to print out stuff. Seems like this render function is constantly receiving update. I'm not sure if this is normal as my reactjs knowledge is not enough to answer this question. It works, but I'm wondering if this is the right way to do things
I solved my problem by calling another function, so my code looks like
class MyList extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
this.props.loadMystuff().then(() => {
if (this.state.eKey !== this.props.eKey) {
let eKey = this.props.eKey;
this.fetchSList(eKey);
}
}).catch((error) => toast(error.message));
}
fetchSList(eKey) {
if (eKey !== '') {
fetch(`some_api_url_config/${this.props.entityKey}`)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
}
}
render() {
const { error, isLoaded, items } = this.state;
if (items) {
return (
<div>
<h3>S List</h3>
<ul>
{items.map(item => (
<li key={item}>
{item}
</li>
))}
</ul>
</div>
);
} else if (error) {
return <div>Error: List Missing...{error.message}</div>;
} else if (!isLoaded) {
return <div>Loading List...</div>;
} else {
return <div>Not Found</div>;
}
}
}
Note that the loadMyStuff is the one retrieving the eKey, so calling that will effectively get me the eKey to resolve the timing issue

Display a limited number of items in the filtered array with a load more button

I have this component Cat that pulls the data from a local json file and displays all the Cats from the file in the alphabetical order. I need to display first 10 cats, and then have a Load More button to display the rest. Does anyone have a solution on how to do it in a good way? The line {providerNumber.length} Cats still needs to show the total number of cats, not the first 10.
Thank you!
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import Error from './Error.jsx';
export default class Cat extends React.Component{
constructor() {
super();
this.state = {
providersData: [],
loading: true
};
}
componentDidMount () {
setTimeout(() => this.setState({ loading: false }), 500);
fetch('../feed/sample.json')
.then(response => { console.log(response); return response.json()})
.then(responseData => {
console.log(responseData)
this.setState({ providersData: [...responseData.providers].sort((a,b) => {
const aName = a.companyName.toUpperCase()
const bName = b.companyName.toUpperCase()
if (aName < bName) {
return -1;
}
if (aName > bName) {
return 1
}
// names must be equal
return 0
})
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render() {
const { loading } = this.state;
const providerNumber = this.state.providersData.filter(provider => provider.yearStarted >= 2010 && provider.type === 'cat')
if(loading) {
return (
<div> <img src="./../assets/loader.svg" alt=""/></div>
); // render loading when app is not ready
}
return this.state.providersData.length ? (
<div>
<h1>Cats</h1>
<div> {providerNumber.length} Cats</div>
{this.state.providersData.map(function(provider, index) {
if (provider.yearStarted >= 2010 && provider.type === 'cat') {
return (
<div key={index} className="job">
<h2>{provider.companyName}</h2>
<img src={provider.images['Company Logo'].url} alt=""/>
</div>
)
}
})}
</div>
) : <Error />
}
};
You can do something like this before you call .map in your render:
this.state.providersData.slice(0, this.state.numberOfCatsShown).map(...)
You would need to initialize this.state.numberOfCatsShown to 10 in your constructor first.
When you want to display more cats, use a function that looks like this:
showMoreCats() {
const newNumberOfCatsShown = this.state.numberOfCatsShown + 10;
// set it to the length of the array to show all the cats.
this.setState({ numberOfCatsShown: newNumberOfCatsShown });
}

Resources