changing process.env variables with jest in tests - reactjs

I'm trying to test a function, which has multiple if statements with variables from process.env.
I was trying to write a test in jest like this:
beforeEach(() => {
delete process.env.REACT_APP_API_KEY;
delete process.env.REACT_APP_URL;
});
it('no URL', () => {
process.env.REACT_APP_API_KEY = 'api_key';
try {
buildUrl(mockMethod, null);
} catch (err) {
expect(err.message).toBe('REACT_APP_API_KEY is not specified!');
}
});
it('no method', () => {
process.env.REACT_APP_URL = 'mock_url';
process.env.REACT_APP_API_KEY = 'api_key';
try {
buildUrl(mockMethod, null);
} catch (err) {
expect(err.message).toBe('REACT_APP_API_KEY is not specified!');
}
});
But the problem is that the variables don't get deleted after every test. They get cached somehow and not set anymore.
Maybe someone has encountered this problem and has some tips on how to solve it.

I managed to solve my problem, the culprit was that I was deconstructing process.env values in the upper part of the file - not in the function as itself + I was renaming them.
const buildUrl = (method: string, page: number = 1): string => {
const { API_KEY, URL } = process.env;
//...
// vs
const {
API_KEY: MY_API_KEY,
URL: MY_URL
} = process.env;
const buildUrl = (method: string, page: number = 1): string => {
const MY_API_KEY = ...

Related

Custom react hook throwing error "React has detected a change in the order of Hooks called by ProvidePlaidLink."

I'm trying to make a custom react hook for the plaid api's Link feature. My code for the custom hook looks like this:
function useProvidePlaidLink() {
const auth = useAuth();
// #ts-ignore
if (!auth.user) return null;
const [linkToken, setLinkToken] = useState(null);
const fetchToken = useCallback(async () => {
try {
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid
},
url:'/api/createLinkToken',
}
const response = await axios(config);
setLinkToken(response.data.linkToken);
} catch (error) {
}
}, []);
useEffect(() => {
fetchToken();
}, [fetchToken]);
const onSuccess = useCallback(async (publicToken, metadata) => {
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid,
publicToken: publicToken,
},
url: '/api/exchangeLinkToken',
};
try {
const response = await axios(config);
} catch (error) {
}
}, []);
const config = {
token: linkToken,
onSuccess,
}
const { open, exit, ready } = usePlaidLink(config);
return { open, exit, ready, fetchToken }
}
The first 3 lines are calling my custom auth hook to get the users user id. When the page first renders this is undefined, but a split second later it loads in the uid. However for that split second when it's undefined, my plaid link code throws errors. So I added a quick if statement to the plaid link code to check if it's defined, and if not then return nothing and don't execute the code. This then throws the error React has detected a change in the order of Hooks called by ProvidePlaidLink. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: I don't think it's that big of a deal because if I just wait for the split second (and no one will be clicking on the link bank button instantly on page load) then I am able to run the function no problem. However I don't like seeing that error so how should I go about fixing this?
Thanks
In the beginning, you check if auth.user is null, and if it is, you return out of the function. This will cause that every hook after the null check will not be run, and that will cause the error.
To solve this, you have to run the hooks even if user is null.
This code should solve the problem:
function useProvidePlaidLink() {
const auth = useAuth();
const [linkToken, setLinkToken] = useState(null);
const fetchToken = useCallback(async () => {
try {
// #ts-ignore
if (!auth.user) return;
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid
},
url:'/api/createLinkToken',
}
const response = await axios(config);
setLinkToken(response.data.linkToken);
} catch (error) {
}
}, []);
useEffect(() => {
fetchToken();
}, [fetchToken]);
const onSuccess = useCallback(async (publicToken, metadata) => {
// #ts-ignore
if (!auth.user) return;
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid,
publicToken: publicToken,
},
url: '/api/exchangeLinkToken',
};
try {
const response = await axios(config);
} catch (error) {
}
}, []);
const config = {
token: linkToken,
onSuccess,
}
const { open, exit, ready } = usePlaidLink(config);
return { open, exit, ready, fetchToken }
}

NextJS Dynamic Rendering

Long time developer finally picking up Next.js, so I know this is probably going to boil down to something silly. Here goes. What's wrong with my getStaticPaths() value here? It seems like I've formatted it exactly as the docs require. (Value being assigned to paths is console.log()'d in the terminal window)
export const getStaticPaths = async () => {
const paths = getEvents();
return {
paths,
fallback: false
};
};
And the getEvents() function:
export const getEvents = () => {
axios.post(`${globals.api_endpoint}getEvents.php`, {
action: 'getStaticPaths'
}).then((r) => {
if (!r.data.error) {
const paths = r.data.map(index => {
return {
params: {
id: index.event_id
}
};
});
console.log(paths);
return paths;
}
});
};
The getStaticPath is an async function. If you're doing something like this paths will always be a Promise here.
const paths = getEvents();
return {
paths,
fallback: false
};
You should use an await keyword here to wait for the results:
const paths = await getEvents();
and in the getEvents function you should return all the axios.post call, like so:
return axios.post(`${globals.api_endpoint}getEvents.php`, {...
Additionally, I don't know how your api endpoint looks but the api path should look like this: ${globals.api_endpoint}/getEvents.php. Your api endpoint shouldn't have the slash at the end.
Gorgeous. Thanks, #krybinski for the help. Of course it's returning a promise. The mistake wasn't quite as silly as I expected, but something simple, for sure.
export const getEvents = async () => {
return axios.post(`${globals.api_endpoint}getEvents.php`, {
action: 'getStaticPaths'
});
};
export const getStaticPaths = async () => {
const response = await getEvents();
const paths = response.data.map(event => {
return {
params: {
id: event.event_id
}
}
});
return {
paths,
fallback: false
};
};

mocking my fetch function does not work, keeps getting back undefined

I am trying to mock a simple function that uses fetch. The function in question looks like this:
export const getPokemon = async () => {
//function that makes the API call and fetches our pokemon
//getPokemon.js
const randomId = () => Math.floor(Math.random() * 151 + 1);
const pokemonApiUrl = `https://pokeapi.co/api/v2/pokemon/`;
export const getPokemon = async () => {
//function that makes the API call and fetches our pokemon
const id = randomId();
let pokemon = { name: "", image: "" };
try {
const result = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
console.log(result)
const data = await result.json();
pokemon.name = data.name;
pokemon.image = data.sprites.other["official-artwork"].front_default;
return pokemon;
} catch (err) {
console.error(err);
Whenever I try to mock the function in my unit tests I receive back a TypeError: Cannot read property 'json' of undefined. Basically, the result comes back as undefined and thus we cannot call our .json(). It works fine in production and the fetch calls work as expected. I am using React Testing Library and Jest.
I have tried to replaced the global fetch in the following manner:
//PokemonPage.test.js
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { name: 'Charizard' } }),
})
);
I've also tried to create a fakeFetch and send it in to my function as a dependency injection, but I get the exact same error.
Lastly, I've tried to install jest-fetch-mock but yet again I am getting the same error... Has anyone encountered the same thing?
The failing function gets called in production here:
function Pokemon({ pokemonTrainer }) {
...
useEffect(() => {
async function fetchData() {
pokemonRef.current = await getPokemon();
setPokemonList((prev) => [
...prev,
{ name: pokemonRef.current.name, image: pokemonRef.current.image },
]);
}
fetchData();
}, []);
...
}

Error: Response is not valid JSON object on firebase function with onCall

Here is my cloud function code
exports.markThemAllRead = functions.https.onCall((data) => {
const user = {
id: data.id,
}
let batch = admin.firestore().batch();
admin.firestore().collection("Notifications").doc("comments").collection(user.id).get().then(querySnapshot => {
if(querySnapshot.empty){
return "nice"
}else {
querySnapshot.forEach(doc=>{
batch.update(doc.ref, {isRead:true})
});
return batch.commit()
}
}).catch(err => {
console.log(err)
})
return "Good"
})
I tried many combinations of return statements, but I keep getting Error: Response is not valid JSON object. Can anyone guide me through what could the issue be? I have many other working onCall functions in my code, but this is one is the only one with the batch.update()... perhaps that has something to do with it?
EDIT ATTEMPT
Still getting the same error when trying this:
exports.markThemAllRead = functions.https.onCall((data) => {
const user = {
id: data.id,
}
return markThemRead(user)
})
async function markThemRead(user){
let batch = admin.firestore().batch();
const docsRef = admin.firestore().collection("Notifications").doc("comments").collection(user.id)
const docs = await docsRef.get();
docs.forEach(function(doc){
batch.update(doc.ref, {isRead:true})
})
return batch.commit()
}
Based on the example of in the Firebase documentation Sync, async and promises
async function markThemRead(user){
let batch = admin.firestore().batch();
const docsRef = admin.firestore().collection("Notifications").doc("comments").collection(user.id)
const docs = await docsRef.get();
docs.forEach(function(doc){
await batch.update(doc.ref, {isRead:true})
})
return batch.commit().then(function () { return {status: "All Good"}})
}

Axios Mock Adapter with repeated params

I am am using a mock adapter for my tests in a react app, and one of them looks like this:
http://endpoint.com/api/entities?id=123&select=labels&select=versions&labelsLang=en
The important part to note is that the select parameter is in there twice.
One of the tests is rendering in another language, so we have two mock endpoints set up to reflect this, however I cannot seem to find a way to properly mock the repeated paramter. I just keep on getting a result for the first one.
The code for the mocked endpoints is this:
const mockApiClient = axios.create({ baseURL: "http://localhost" });
const mock = new MockAdapter(mockApiClient);
const params1 = new URLSearchParams();
params1.append("id", "123");
params1.append("select", "labels");
params1.append("select", "versions");
params1.set("labelsLang", "en");
mock
.onGet("/entities", {
asymmetricMatch: function(actual: any) {
return actual.params.toString() === params1.toString();
},
})
.reply(200, getCompanyResponse);
const params2 = new URLSearchParams();
params2.append("id", "123");
params2.append("select", "labels");
params2.append("select", "versions");
params2.set("labelsLang", "de");
mock
.onGet("/entities", {
asymmetricMatch: function(actual: any) {
return actual.params.toString() === params2.toString();
},
})
.reply(200, getCompanyResponseDE);
I know it's messy, I just want to understand how to do this properly.
Whenever I try specifying specific params in an object, it complains that you cant have a duplicate key .
(ie.
{ params:{select:'labels', select:'versions} })
Solved.
Here's how it was done:
const mockApiClient = axios.create({ baseURL: "http://localhost" });
const mock = new MockAdapter(mockApiClient);
const params1 = {
"id": "123",
select: ["labels", "versions"],
"labelsLang": "en",
};
mock
.onGet("/entities", {
params: {
asymmetricMatch: function(actual: any) {
actual.sort(); // Important, as without it the order of params would affect the result
return actual.toString() === toURLSearchParams(params1).toString();
},
},
})
.reply(200, getCompanyResponse);
export const toURLSearchParams = (params: {[key: string]: string|string[]}, sort:boolean = true):URLSearchParams => {
if(params instanceof URLSearchParams) return params
const searchParams = new URLSearchParams();
for(const key in params){
const value = params[key];
if(Array.isArray(value)){
value.forEach((eachValue) => {
searchParams.append(key, eachValue)
})
} else {
searchParams.append(key,value)
}
}
if(sort) searchParams.sort() // Likewise here.
return searchParams;
}

Resources