Testing a Recoil selector with async Query - reactjs

I am using Recoil for state management and have problem testing my selector.
It seems mocking a function inside a selector is not possible.
My code is similar to the code below:
const currentUserNameQuery = selector({
key: 'CurrentUserName',
get: async ({get}) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.name;
},
});
function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
}
Now, I want to mock the return value of myDBQuery but it seems I cannot do that when using selector. I was wondering if there is any way to mock returned result of a function inside selector.

How about moving the logic from get into a separate file? After that, you can mock this function.

Related

How do I separate api / async request logic from react components when using recoil

So at the moment I am having to put my request / api logic directly into my components because what I need to do a lot of the time is set state based on the response I get from the back end.
Below is a function that I have on my settings page that I use to save the settings to recoil after the user hits save on the form:
const setUserConfig = useSetRecoilState(userAtoms.userConfig);
const submitSettings = async (values: UserConfigInterface) => {
try {
const { data: {data} } = await updateUser(values);
setUserConfig({
...data
});
} catch (error) {
console.log('settings form error: ', error);
}
}
This works perfectly...I just dont want the function in my component as most of my components are getting way bigger than they need to be.
I have tried making a separate file to do this but I can only use the recoil hooks (in this instance useSetRecoilState) inside of components and it just complains when I try and do this outside of a react component.
I have tried implementing this with recoils selector and selectorFamily functions but it gets kind of complicated. Here is how I have tried it inside of a file that has atoms / selectors only:
export const languageProgress = atom<LanguageProgress>({
key: "LanguageProgress",
default: {
level: 1,
xp: 0,
max_xp: 0
}
})
export const languageProgressUpdate = selectorFamily<LanguageProgress>({
key: "LanguageProgress",
get: () => async () => {
try {
const { data: { data } } = await getLanguageProgress();
return data;
} catch (error) {
console.log('get language progress error');
}
},
set: (params:object) => async ({set}) => {
try {
const { data: { data } } = await updateLanguageProgress(params);
set(languageProgress, {
level: data.level,
xp: data.xp,
max_xp: data.max_xp
});
} catch (error) {
console.log('language progress update error: ', error);
}
}
});
What I want to do here is get the values I need from the back end and display it in the front which I can do in the selector function get but now I have 2 points of truth for this...my languageProgress atom will initially be incorrect as its not getting anything from the database so I have to use useGetRevoilValue on the languageProgressUpdate selector I have made but then when I want to update I am updating the atom and not the actual value.
I cannot find a good example anywhere that does what I am trying to here (very suprisingly as I would have thought it is quite a common way to do things...get data from back end and set it in state.) and I can't figure out a way to do it without doing it in the component (as in the first example). Ideally I would like something like the first example but outside of a component because that solution is super simple and works for me.
So I dont know if this is the best answer but it does work and ultimately what I wanted to do was seperate the logic from the screen component.
The answer in my situation is a bit long winded but this is what I used to solve the problem: https://medium.com/geekculture/crud-with-recoiljs-and-remote-api-e36581b77168
Essentially the answer is to put all the logic into a hook and get state from the api and set it there.
get data from back end and set it in state
You may be looking for useRecoilValueLoadable:
"This hook is intended to be used for reading the value of asynchronous selectors. This hook will subscribe the component to the given state."
Here's a quick demonstration of how I've previously used it. To quickly summarise: you pass useRecoilValueLoadable a selector (that you've defined somewhere outside the logic of the component), that selector grabs the data from your API, and that all gets fed back via useRecoilValueLoadable as an array of 1) the current state of the value returned, and 2) the content of that API call.
Note: in this example I'm passing an array of values to the selector each of which makes a separate API call.
App.js
const { state, contents } = useRecoilValueLoadable(myQuery(arr));
if (state.hasValue && contents.length) {
// `map` over the contents
}
selector.js
import { selectorFamily } from 'recoil';
export const myQuery = selectorFamily({
key: 'myQuery',
get: arr => async () => {
const promises = arr.map(async item => {
try {
const response = await fetch(`/endpoint/${item.id}`);
if (response.ok) return response.json();
throw Error('API request not fulfilled');
} catch (err) {
console.log(err);
}
});
const items = await Promise.all(promises);
return items;
}
});

React useEffect with Stripe - get client secret asynchronously

I've been plopped into the middle of a react project with no prior knowledge so I'm sure this a duplicate.
I want to use the StripeElements, and the react library is easy enough. I'm just not sure how to get the client secret from the server.
This is what I'm trying:
const stripePromise = loadStripe('stripe_key');
const StripeForm = () => {
const [stripeData, setStripeData] = useState({});
const getClientSecret = async () => {
const { data } = await api.get(`payment/stripe/get-client-secret/`);
return data
}
useEffect(() => {
getClientSecret().then(data => setStripeData(data))
}, [])
if(stripeData.clientSecret) {
return (
<QuoteContainer title={"Pay With Credit Card"}>
<SectionCard>
{stripeData.clientSecret}
</SectionCard>
</QuoteContainer>
);
}
return (<b>Loading</b>)
}
The route payment/stripe/get-client-secret/ returns an array with a key 'clientSecret'. This function is working correctly.
I just can't get the <b>Loading</b> text to be replaced with the QuoteContainer component once the promise is resolved.
If you want to rendering Loading component. You have to set falsy value for stripedData state. Because {} value is truthy. So I think the code is not rendering Loading component. The loading component is not rendered because there is no place to set it as a false value anywhere.
what AJAX library are you using to make the API call? Usually you need to call a function on the response object in order to access the data. For instance, if you are using fetch(), you need to call json() then access the response data.

How to partially mock a custom react hook with Jest?

I would like to mock some of my custom React hook return value with Jest.
Here is an example of what i'm trying to do :
export default function useSongPlayer() {
const playSongA = () => {...}
const playSongB = () => {...}
const playSongC = () => {...}
return {
playSongA,
playSongB,
playSongC,
}
}
Now in my test, I would like to mock only playSongA and keep the other functions real implementations. I tried several approachs, including someting with jest.requireActual('hooks/useSongPlayer') but it seems that playSongA is successfully mocked while the others are just undefined.
With something like this, i'm pretty sure to achieve what i want
export function playSongA() {...}
export function playSongB() {...}
export function playSongC() {...}
The problem seems to be the module I try to mock is a function, and I want to partially mock the result of this function.
This is possible if you create a mock that returns the original, but also intercepts the function calls and replaces one of the values.
You can do this without a Proxy, but I choose to demonstrate that as it's the most complete method to intercept any use of the actual hook.
hooks/__mocks__/useSongPlayer.js
// Get the actual hook
const useSongPlayer = jest.requireActual('hooks/useSongPlayer');
// Proxy the original hook so everything passes through except our mocked function call
const proxiedUseSongPlayer = new Proxy(useSongPlayer, {
apply(target, thisArg, argumentsList) {
const realResult = Reflect.apply(target, thisArg, argumentsList);
return {
// Return the result of the real function
...realResult,
// Override a single function with a mocked version
playSongA: jest.fn(),
};
}
});
export default proxiedUseSongPlayer;

Jest - how to mock a class in jest

I've been trying to mock a test in jest through the methods that they have on their documentation. By mocking the whole class but I can't seem to get it to work properly.
https://jestjs.io/docs/en/es6-class-mocks
jest.mock('../../../../../src/SubscriptionOrder');
SubscriptionOrder.prototype.createChargebeeSubscription = jest.fn(() => 'response');
const test = new SubscriptionOrder(
'subscription',
[{}],
'errorMethods',
'customerMethods',
);
test.createChargebeeSubscription();
I'd expect this to mock the createChargebeeSubscription method and return the string response but it seems to be returning undeifined
Then this is the piece of code I'm trying to run a test for as well.
const subscriptionOrder = new SubscriptionOrder(
'subscription',
subscriptionRequest,
errorMethods,
customerMethods,
);
const response = await subscriptionOrder.createChargebeeSubscription(token);
this.setState({ successfulSubmit: response });
I want to update the state to the string response but getting undefined instead. so it appears I'm kinda mocking something but just not properly.
You can use spyOn as follows to do the mocking for you. I also recommend that you set up and tear down this spy once you are finished.
So here's a sample piece of code which will do what you want:
describe('createChargebeeSubscription() method behaviour', () => {
let createChargebeeSubscriptionSpy;
let testResponse;
beforeAll(() => {
// Lets create an instance of your class first
const subscriptionOrder = new SubscriptionOrder(
'subscription',
subscriptionRequest,
errorMethods,
customerMethods
);
// Now use a spy to mock the return value from your function
createChargebeeSubscriptionSpy = jest.spyOn(subscriptionOrder, 'createChargebeeSubscription').mockImplementation(() => {
return 'response';
});
// Finally invoke the method being tested
testResponse = subscriptionOrder.createChargebeeSubscription();
});
afterAll(() => {
// Restore the functionality (ie. disable the spy) of your method
createChargebeeSubscriptionSpy.mockRestore();
});
it('verifies that the expected response was returned', () => {
expect(testResponse).toBe('response');
});
});

How to mock client.readQuery from Apollo 2 in Jest

There's a component which I am using the cache that I created in apollo, stored inside the client.
At some point, as a callback from an input, I do the following call:
const originalName = client.readQuery<NamesCached>({ query: NamesCached })!.names
this returns directly an array of objects.
However, I do not know how to test this in my component. My first assumption was to mock exclusively the client.readQuery to return me an array from my __fixtures__. That didn't work.
Here is my best shot to mock it:
it('filters the name in the search', () => {
jest.doMock('#/ApolloClient', () => ({
readQuery: jest.fn()
}))
const client = require('#/ApolloClient')
client.readQuery.mockImplementation(()=> Promise.resolve(names()))
const { getByPlaceholderText, queryByText } = renderIndexNames()
const input = getByPlaceholderText('Search…')
fireEvent.change(input, { target: { value: 'Second Name' } })
expect(queryByText('Title for a name')).not.toBeInTheDocument()
})
For this test I am using react-testing-library. This is my best shot so far...
jest.doMock('#/ApolloClient', () => ({
readQuery: jest.fn()
}))
const client = require('#/ApolloClient')
client.readQuery.mockImplementation(()=> Promise.resolve(names()))
It is important that relies on a single IT, not on the top since there are other tests that are using the client and works properly.
With That I could had mocked in the test when invokin client.readQuery() but then in the component itself goes back to its own original state
My goal
Find a way that the client.readQuery can be mocked and return the data I am looking for. I thought in mocking, but any other solution that can work for a single or group of tests( without all of them) would be more than welcome.
I tried as well mocking on the top, but then the others are failing and i couldn't reproduce to go back to the real implementation

Resources