How to Test Apollo mutation with one variable set to random? - reactjs

I am working on testing my Components using Appolo Mock provider. However, I have this mutation query wherein one of my variables is set to a random UUID. How could I test it? It is giving me an error of no mock response for this query since my query does not match my mock please help tnx.
Component
const [createMenuProduct, { loading }] = useMutation(CREATE_MENU_PRODUCTS);
createMenuProduct({
variables: {
menuId: menuId,
id: uuid(),
productId: selectedProduct,
},
});
test Mock
{
request: {
query: CREATE_MENU_PRODUCTS,
variables: {
menuId: menuId,
id: uuid(),
productId: '4b1b6048-6cb1-46e0-ab4d-80fd11ebeacb',
},
},
result: {
data: {
insertMenuProducts: {
returning: [
{
menu_id: 'b591993d-af18-4bf5-88ad-26f08691afc7',
product_id: '4b1b6048-6cb1-46e0-ab4d-80fd11ebeacb',
product: {
variant: {
id: '04befbe6-9635-4dde-abc2-673af13eb462',
isDeleted: false,
productVariantAddOns: [],
},
},
},
],
},
},
},
},
currenly iam encountering this error due to I cannot match my mock variable with what is expected

You could mock the returned value by uuid() to have the same as in the mock
const uuidVariable = 'mocked-uuid';
...
{
request: {
query: CREATE_MENU_PRODUCTS,
variables: {
menuId: menuId,
id: uuidVariable,
productId: '4b1b6048-6cb1-46e0-ab4d-80fd11ebeacb',
...
in your test
import uuid from 'uuid/v4';
jest.mock('uuid/v4');
describe('some component', () => {
it('call mutation', () => {
uuid.mockImplementation(() => uuidVariable);
// here render component and interact to fire the mutation
});
});

Related

Why change State inside setTimeout make an infinity loop

I have a functional component and I want to change State inside a setTimeout, but I don't know why it causes the component to be in an infinite loop!
Here is my Functional Component:
import { useState } from "react";
function Card() {
const [booksState, setBooks] = useState({
books: [
{ name: "Harry", id: 1 },
{ name: "Potter", id: 2 },
{ name: "John", id: 3 },
],
});
console.log("test");
setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 },
];
setBooks({
books: [...booksState.books, ...newBooks],
});
}, 2000);
return <div>TEST</div>;
}
export default Card;
The console log:
Set Timeout runs every time the component renders. This means every time the state changes, it starts a new timer. Assuming you only want to run the timer when the component first renders, you should use the useEffect hook as follows:
import { useState } from "react";
function Card() {
const [booksState, setBooks] = useState({
books: [
{ name: "Harry", id: 1 },
{ name: "Potter", id: 2 },
{ name: "John", id: 3 },
],
});
console.log("test");
useEffect(() => {
setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 },
];
setBooks({
books: [...booksState.books, ...newBooks],
});
}, 2000)
}, []);
return <div>TEST</div>;
}
export default Card;
You need to call setTimeout inside a useEffect to avoid infinite calling of setTimeout.
In the current implementation, setTimeout gets called again after the first timeout completes (due to rerendering) which causes it to exponentially accumulate the timeout calls.
useEffect(() => {
setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 }
];
setBooks({
books: [...booksState.books, ...newBooks]
});
}, 2000);
}, []);
In your current implementation setTimeout runs every time the component renders means on every state update so because of that it's getting called again and again. So, you need to call setTimeout inside of useEffect. And don't forget to clearTimeout in the useEffect's cleanup function.
useEffect(() => {
const myTimeout = setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 },
];
setBooks({
books: [...booksState.books, ...newBooks],
});
}, 2000)
return () => {
clearTimeout(myTimeout)
}
}, []);

Why does forEach loop only set the last value if finds to state. ReactJS

const CategoriesData = [
{
name: "Category1",
isActive: true,
children: [
{
name: "Category1Child",
isActive: false,
}
]
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3",
isActive: true,
children: [
{
name: "Category3Child",
isActive: false,
}
]
}
];
const [disabledCategories, setDisabledCategories] = useState([]);
function notActiveCategories(categories) {
// Loop logs out at least 7 isActive: false categories.
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories([...disabledCategories, category]);
console.log(category);
}
});
};
useEffect(() => {
notActiveCategories(CategoriesData);
console.log(disabledCategories); // Only 1 category is in the array.
}, []);
I feel like the function the loop is in calling itself is causing the disabledCategories state to revert to when it was empty and that is leading to only the last step of the foreach to be set.
So how would i get this to loop through the categories array and have the disabledCategories state to contain all of the category objects that have isActive: false.
Which in the example of CategoriesData above, it would mean that the disabledCategories state would contain:
[
{
name: "Category1Child",
isActive: false,
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3Child",
isActive: false,
},
];
Try changing your setDisabledCategories to use the previous state param that comes from setState:
setDisabledCategories(prevState => [...prevState, category])
When multiple setState calls are batched together you need to be careful so they don't override each other. Using this method ensures that your setState calls are "chained" so you always get the updated state.
Way 1: Affect after recursive loop
function notActiveCategoriesRecusive(categories) {
let notActive = []
categories.forEach((category) => {
if (category.isActive) notActive = [...notActive, ...(notActiveCategories(category.children))];
if (!category.isActive) {
notActive.push(category)
}
});
return notActive
};
function notActiveCategories(categories) {
setDisabledCategories(notActiveCategoriesRecusive(categories)
}
Way 2: Get the last state because it doesn't has time to refresh
function notActiveCategories(categories) {
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories(oldState => ([...oldState, category]))
}
});
};
I'd only call setState once with the filtered array:
const findInactive = data =>
data.filter(e => !e.isActive)
.concat(...data.filter(e => e.children)
.map(e => findInactive(e.children)))
;
const categoriesData = [ { name: "Category1", isActive: true, children: [ { name: "Category1Child", isActive: false, } ] }, { name: "Category2", isActive: false, }, { name: "Category3", isActive: true, children: [ { name: "Category3Child", isActive: false, } ] } ];
const inactive = findInactive(categoriesData)
// the following is neeeded if it's possible for a
// node to have children and be inactive
// .map(({name, isActive}) => ({name, isActive}))
;
console.log(inactive);
//setDisabledCategories(inactive); // one time in React
This makes the code a lot easier to reason about and decouples React's API out from the filtering logic, which can be moved out to a generic function agnostic of React.
As others have mentioned, if you do want to call setState multiple times as a batch update, you can use the prevState callback to chain the updates: setDisabledCategories(prevState => [...prevState, category]);.

Testing Axios in Jest

I'm new to testing.
I'm trying to test an asynchronous data fetching function but I can't figure out why the test doesn't pass.
I have mocked Axios with jest and gave Axios' get method a mock implementation to resolve a promise.
The error says it can't read the property of name with means the data obj is undefined I reckon.
Here's Yelp.test.js
import Yelp from './Yelp';
import axios from 'axios';
jest.mock('axios');
describe('searchRestaurantsInfo', () => {
test('returns object with restaurant infos', async () => {
const data = {
name: 'Casa Romana',
address: '5 Albion Street',
coordinates: { lat: 52.6322649, lng: -1.1314474 },
city: 'Leicester LE1 6GD',
rating: 4.5,
photos: [
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
],
phone: '+441162541174',
price: '£££',
categories: 'Italian',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm',
reviews: [
{
id: 'i_Q39aN9hwZzGDUb-IWpYw',
rating: 5,
text:
'Proper Italian restaurant. Not Italian-themed, or serving Italian fusion cuisine, just a place with an Italian owner who makes solid, straightforward...',
time_created: '2014-10-02 03:49:36',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&hrid=i_Q39aN9hwZzGDUb-IWpYw&utm_campaign=yelp_api_v3&utm_me',
user: {
id: '6tPD46XZSFllvgn2vTh51A',
image_url:
'https://s3-media3.fl.yelpcdn.com/photo/A4Ww6Ks2P9WsALqOFy9cOA/o.jpg',
name: 'Espana S.',
profile_url:
'https://www.yelp.com/user_details?userid=6tPD46XZSFllvgn2vTh51A',
},
},
],
};
axios.get.mockImplementationOnce(() => Promise.resolve(data));
await expect(
Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
).resolves.toEqual(data);
});
});
And Yelp.js
import axios from 'axios';
let YELP_API_KEY = process.env.REACT_APP_YELP_API_KEY;
const Yelp = {
// Provides infos about a single restaurant
async searchRestaurantsInfo(id) {
try {
let response = await axios.get(
`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/${id}`,
{
headers: {
Authorization: `Bearer ${YELP_API_KEY}`,
'X-Requested-With': 'XMLHttpRequest',
'Access-Control-Allow-Origin': '*',
},
}
);
let responseRew = await axios.get(
`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/${id}/reviews`,
{
headers: {
Authorization: `Bearer ${YELP_API_KEY}`,
'X-Requested-With': 'XMLHttpRequest',
'Access-Control-Allow-Origin': '*',
},
}
);
const parameters = {
name: response.data.name,
address: response.data.location.display_address[0],
coordinates: {
lat: response.data.coordinates.latitude,
lng: response.data.coordinates.longitude,
},
city: response.data.location.display_address[1],
rating: response.data.rating,
photos: response.data.photos,
phone: response.data.phone,
price: response.data.price,
categories: response.data.categories[0].title,
url: response.data.url,
reviews: responseRew.data.reviews,
};
console.log({ parameters, id });
return parameters;
} catch (e) {
console.log(e);
return e;
}
}}
The error I get is
searchRestaurantsInfo
× returns array of restaurnats obj (66ms)
● searchRestaurantsInfo › returns array of restaurnats obj
expect(received).resolves.toEqual(expected) // deep equality
- Expected
+ Received
- Object // data object. I removed it from this error message because too long
+ [TypeError: Cannot read property 'name' of undefined]
47 | await expect(
48 | Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
> 49 | ).resolves.toEqual(data);
| ^
50 | });
51 | });
52 |
at Object.toEqual (node_modules/react-scripts/node_modules/expect/build/index.js:202:20)
at Object.<anonymous> (src/helpers/Yelp.test.js:49:16)
console.log src/helpers/Yelp.js:91
TypeError: Cannot read property 'name' of undefined
at Object.searchRestaurantsInfo (C:\Users\Turi\Desktop\project\RestaurantsRedux\src\helpers\Yelp.js:72:29)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at Object.<anonymous> (C:\Users\Turi\Desktop\project\RestaurantsRedux\src\helpers\Yelp.test.js:47:5)
Thanks in advance for your help!
There might be a problem with how you wait for the result (possible compilation issue), try writing the test like this.
// note make sure the test() function is async
const result = await Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
expect(result).toEqual(data);
I've managed to find the solution.
Like suggested I had to add another mock since in the function there are two different request.
In addition to that I realised I couldn't use data in both
axios.get.mockImplementationOnce(() => Promise.resolve(data));
and
Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
).resolves.toEqual(data);```
since the function wasn't returning data but an object with some parts from data.
Therefore I created a new object params to be compared with the function returned object.
import Yelp from './Yelp';
import axios from 'axios';
jest.mock('axios');
describe('searchRestaurantsInfo', () => {
test('returns object with restaurant infos', async () => {
const response = {
data: {
name: 'Casa Romana',
location: {
display_address: [
"12 Upper Saint Martin's Lane",
'London WC2H 9FB',
'United Kingdom',
],
},
coordinates: { latitude: 52.6322649, longitude: -1.1314474 },
rating: 4.5,
photos: [
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
],
phone: '+441162541174',
price: '£££',
categories: [{ alias: 'indpak', title: 'Indian' }],
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm',
},
};
const responseRev = {
data: {
reviews: [
{
id: 'i_Q39aN9hwZzGDUb-IWpYw',
rating: 5,
text:
'Proper Italian restaurant. Not Italian-themed, or serving Italian fusion cuisine, just a place with an Italian owner who makes solid, straightforward...',
time_created: '2014-10-02 03:49:36',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&hrid=i_Q39aN9hwZzGDUb-IWpYw&utm_campaign=yelp_api_v3&utm_me',
user: {
id: '6tPD46XZSFllvgn2vTh51A',
image_url:
'https://s3-media3.fl.yelpcdn.com/photo/A4Ww6Ks2P9WsALqOFy9cOA/o.jpg',
name: 'Espana S.',
profile_url:
'https://www.yelp.com/user_details?userid=6tPD46XZSFllvgn2vTh51A',
},
},
],
},
};
const params = {
name: 'Casa Romana',
address: "12 Upper Saint Martin's Lane",
coordinates: { lat: 52.6322649, lng: -1.1314474 },
city: 'London WC2H 9FB',
rating: 4.5,
photos: [
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
],
phone: '+441162541174',
price: '£££',
categories: 'Indian',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm',
reviews: [
{
id: 'i_Q39aN9hwZzGDUb-IWpYw',
rating: 5,
text:
'Proper Italian restaurant. Not Italian-themed, or serving Italian fusion cuisine, just a place with an Italian owner who makes solid, straightforward...',
time_created: '2014-10-02 03:49:36',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&hrid=i_Q39aN9hwZzGDUb-IWpYw&utm_campaign=yelp_api_v3&utm_me',
user: {
id: '6tPD46XZSFllvgn2vTh51A',
image_url:
'https://s3-media3.fl.yelpcdn.com/photo/A4Ww6Ks2P9WsALqOFy9cOA/o.jpg',
name: 'Espana S.',
profile_url:
'https://www.yelp.com/user_details?userid=6tPD46XZSFllvgn2vTh51A',
},
},
],
};
axios.get.mockImplementationOnce(() => Promise.resolve(response));
axios.get.mockImplementationOnce(() => Promise.resolve(responseRev));
await expect(
Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
).resolves.toEqual(params);
});
});

AssertionError [ERR_ASSERTION]: Mocks not yet satisfied: on using chaining with nock while testing Redux actions

I am trying to test an action in a React project with redux.
The test i am trying to do using Mocha,Enzyme is for a DELETE_USER action which is dispatched first on pressing a delete button and onSuccess of the action another action LOAD_ALL_USERS is dispatched which performs a get request.
Following is the code for test i tried , which gives the error as in the topic:
it('should call the /users endpoint with a user ID', (done) => {
const deleteUserResponse = {userId: 1337};
const mapActions = (actions) => ({
deleteActions: filterForAction(actions, 'DELETE_USER'),
loadAllUserActions: filterForAction(
actions,
'LOAD_ALL_USERS'
)
});
const loadAllUsersResponse =
[
{
id: 1337,
email: 'testuser9#some-company.com',
firstName: 'John',
lastName: 'Doe',
active: false
},
{
id: 1338,
email: 'adamsmith#mail.com',
firstName: 'Adam',
lastName: 'Smith',
active: true
}
];
const sampleApiSearchParams = {
locale: 'en-GB',
pageSize: 10,
sortBy: 'userId',
sortDirection: 'desc',
location: 'Zurich'
};
const sampleReactTableSearchParams = {
filtered: [
{id: 'userId', value: '1id'},
{id: 'userFirstName', value: 'fname'},
{id: 'userLastName', value: 'lname'},
{id: 'userEmail', value: 'maill'}
],
sorted: [{id: 'userId', desc: true}],
pageSize: 10
};
const scope = nock('http://localhost')
.delete('users/1337')
.reply(200, deleteUserResponse)
.get(uri=>uri.includes('users'),{...sampleApiSearchParams})
.reply(200, loadAllUsersResponse);
const store = mockStore(
{
stringResources: {
'deleteuser.confirm.title': 'User Delete Confirm',
'deleteuser.confirm.description':
'Are you sure you want to delete the user?',
'deleteuser.notification.message.success':
'is successfully deleted.'
},
userManagement: {
table: {
searchParams: sampleReactTableSearchParams
}
}
});
const actual = actions.deleteUser(1337)
store.dispatch(actual);
setTimeout(() => {
scope.done();
const {deleteActions, loadAllUsersActions} = mapActions(store.getActions());
expect(
deleteActions[1].meta['redux-pack/LIFECYCLE']
).toBe('success');
expect(deleteActions[1].payload).toEqual(
deleteUserResponse
);
//expect(loadAllUsersActions[1].payload).toEqual(loadAllUsersResponse);
done();
}, 50);
});
});
If i comment the 'scope.done()' the test passes, but http.get request is not getting called so the 'loadAllUsersActions' is undefined. How can i solve this , what is that i am doing wrong ?
Thanks in advance.

Apollo GraphQL (React) refetchQueries/update after mutation don't update the store

I have a component that behaves like one in Google drive, when you want to move your file/folder. It fetches all the necessary data about directories, displays it, and after one have been chosen - it moves a file into another folder. The goal I'm trying to aim is update data about currently displayed folder and folder where file has been moved. I tried the both way (refetchQueries, update), but non worked...
The main issue is that queries, defined in updateQueries are executed, but store doesn't update.
It would be great, if anyone could help!
const EntityMoveContainer = compose(
graphql(GET_DIRECTORIES, {
options() {/*...*/},
props(props) {/*...*/}
}),
graphql(MOVE_FILE, {
props(props) {
const { mutate, ownProps } = props;
const { entity, directoryId } = ownProps;
return {
async moveFile(destDirId) {
return mutate({
variables: {
fileId: entity.id,
directoryId: destDirId,
},
refetchQueries: () => [
{
query: GET_DIRECTORIES,
variables: {
id: directoryId,
sortKey: store.sortKey,
cursor: store.cursor,
filetype: store.filetype,
},
},
{
query: GET_DIRECTORIES,
variables: {
id: destDirId,
sortKey: store.sortKey,
cursor: store.cursor,
filetype: store.filetype,
},
},
],
/* update(proxy) {
console.log('update method');
try {
const storeData = proxy.readQuery({
query: GET_DIRECTORIES,
variables: {
id: destDirId,
sortKey: store.sortKey,
filetype: store.filetype,
cursor: store.cursor,
},
});
storeData.directory = {
...storeData.directory,
files: {
...storeData.directory.files,
edges: [
...storeData.directory.files.edges,
{ node: entity, __typename: 'File' },
],
},
};
proxy.writeQuery({
query: GET_DIRECTORIES,
variables: {
id: destDirId,
sortKey: store.sortKey,
filetype: store.filetype,
cursor: store.cursor,
},
data: storeData,
});
} catch (e) {
console.log(e);
}
}, */
});
},
};
},
}),
)(EntityMoveView)
The issue was with a
cursor : ''
property I passed into refetchQueries.

Resources