Jest - how to mock network delay - asynchronous

I'm trying to mock a slow network in Jest (with React testing library). The condition I'm trying to set up is something like this:
UI is in a specific state
async AJAX request is fired off
ui changes state
async request completes
It seems to me this would be quite a common requirement, because with asynchronous programming you have to be aware of what might have happened between the AJAX request being sent and when it's completed. However, I can't find any discussion or tools in this area at all.

This reply is quite specific to react-testing-libaray but I think the concept is generally applicable.
First of all I realised that if you start the async operation in response to a user event (say a click) then you don't have a problem - as soon as you've called fireEvent.click, you are in state 3, then you can use waitFor() to wait until you get to state 4.
Secondly, if your environment gives you a handle to the promise for the async operation then of course you can just wait for that so again you don't have a problem.
In my case, the async operation was fired off by a timer, and because it was inside a react component, I get a handle to that timer from the outside. So my scenario is something like this:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "initial" };
}
componentDidMount = () => setTimeout(async () => {
this.setState({ message: "loading" });
var message = await this.props.longRunningOperation();
this.setState({ message });
}, 5000);
render = () => <div>{this.state.message}</div>
}
The component state is initially "initial", 5 seconds after it mounts, it fires off the long running operation and sets the state to "loading", then when it gets a response, it sets the state to the text of that response. My test case looks like this:
test("long running response", async () => {
const fakeLongRunningOperation = async () => new Promise(resolve =>
setTimeout(() => resolve("success"), 2000));
jest.useFakeTimers();
var app = render(<App longRunningOperation={fakeLongRunningOperation} />);
jest.advanceTimersByTime(4000)
expect(app.queryByText("initial")).not.toBeNull();
jest.advanceTimersByTime(2000)
expect(app.queryByText("loading")).not.toBeNull();
jest.advanceTimersByTime(2000)
await waitFor(() => { expect(app.queryByText("success")).not.toBeNull() });
});
We fake the long running operation with a function that returns "success" after a 2 second delay. After 4 seconds, we expect the state to still be "initial", then after another 2 seconds we expect it to be "loading" and after another 2 seconds, we expect it to be "success". And we use jest fake timers so the test doesn't actually take 8 seconds to run.
Hope that helps anyone else who is trying to do a similar kind of thing.

Related

Next JS pages/api; having trouble calling influxdb client from api

I have written a handler function inside my nextjs page/api folder;
handler(req, res) {}
Am using #influxdata/influxDb-client as mentioned in the documentation. Am using
from(queryAPI.rows(query).pipe(....).subscribe(next(value)=> {results.push(value}, complete(console.log(results); res.status(200).json(results)}
Am getting all the query value, once the observable is completed. it works most of the time.
Am pushing the intermediate results in the next part of the subscriber and trying to send the results back to client in the complete part of the subscriber. I want the request handler to wait till i get all my values from influx DB query in the complete part of the subscriber and can send the value back to client..
But the issue "Handler function will not Wait till the observable is completed". Handler function returns, before the observer gets completed. Am getting error: API resolved without sending a response...
I get all the values only when the observer is completed.
I don't know how to handle the scenario.
How can I make the handler function wait until the observable is completed?
I found the solution for the same
I used new Promise() with await, added my observable inside this promise and resolved the promise on Complete of the subscribe.
Code will look like the following :
export async function handler (req, res) {
const results=[];
await new Promise((resolve, reject) => {
from((queryAPIs.rows(query))
.pipe(map(({values, tableMeta}) => tableMeta.toObject(values)))
.subscribe(
{
next(object) => {results.push(object)}
complete() => { resolve (results) }
error(err) => { reject (err) }
});
res.status(200).send(results);
}
}

Cloudflare Worker times out for the client, but all the work completes and no timeout error given (inspected with console.log/ `wrangler tail`)

My published Cloudflare Worker (wrangler publish --env environment_name) is timing out for clients, but not when
running locally (e.g. by using cfworker, a nice tool to emulate cloudflare workers locally)
or running in preview (wrangler preview --env environment_name).
A summary of my worker:
addEventListener('fetch', async (event) => {
const fetchEvent = event as FetchEvent
const results = await doSomeWork() // returns a promise
return fetchEvent.respondWith(new Response(JSON.stringify(results)))
})
My wrangler tail (production logs) output does complete (after I placed console.log statements in doSomeWork. There were no errors, and I got {"outcome":"ok"... in wrangler tail. I would have expected to get error code 1102 (Worker exceeded CPU time limit.) if time-out was happening.
It turns out that addEventListener cannot be passed an async function (or one that returns a promise). The fetchEvent.respondWith does accept a promise however. This is not written in the documentation, but I discovered this in lib.webworker.d.ts.
To do asynchronous work, you must return a promise to fetchEvent.respondWith instead:
So your alternatives are to:
Pass a promise to respondWith
addEventListener('fetch' (event) => {
const fetchEvent = event as FetchEvent
const responsePromise = doSomeWork().then((results) => new Response(JSON.stringify(results))
return fetchEvent.respondWith(responsePromise))
})
Or pass the result of an async function to respondWith (still a promise, I told you, you must return a promise)
addEventListener('fetch' (event) => {
const fetchEvent = event as FetchEvent
const responsePromise =
return fetchEvent.respondWith(async () => {
// I would put this async function in a different file (handlers.ts/.js), and name it `doSomeWorkHandler` to make it more readable though
const results = await doSomeWork()
return new Response(JSON.stringify({hello: "world"}))
}))
})
Why no timeout error?
The reason the timeout error doesn't happen is because even though Cloudflare Workers limits your CPU execution time to 10ms on the free plan, it doesn't stop your worker because you're not using the CPU in this bug/ edge case. It's doing nothing.

#ngrx Action called Infinitely with Effects

Please forgive me if this is an easy answer. I have a complicated login logic that requires a few calls before a user has a complete profile. If a step fails, it shouldn't break the app -- the user just doesn't get some supplemental information.
The flow I'm looking to achieve is this:
Call Revalidate.
Revalidate calls RevalidateSuccess as well as ProfileGet (supplemental fetch to enhance the user's state).
ProfileGetSuccess.
To save tons of code, the actions exist (it's a giant file).
The app kicks off the action: this._store.dispatch(new Revalidate())
From there, we have the following effects:
#Effect()
public Revalidate: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.REVALIDATE),
map((action: Revalidate) => action),
// This promise sets 'this._profile.currentProfile' (an Observable)
flatMap(() => Observable.fromPromise(this._auth.revalidate())),
// Settings are retrieved as a promise
flatMap(() => Observable.fromPromise(this._settings.get())),
switchMap(settings =>
// Using map to get the current instance of `this._profile.currentProfile`
this._profile.currentProfile.map(profile => {
const onboarded = _.attempt(() => settings[SettingsKeys.Tutorials.Onboarded], false);
return new RevalidateSuccess({ profile: profile, onboarded: onboarded });
}))
);
//Since I couldn't get it working using concatMap, trying NOT to call two actions at once
#Effect()
public RevalidateSuccess: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.REVALIDATE_SUCCESS),
mapTo(new ProfileGet)
);
#Effect()
public ProfileGet: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.PROFILE_GET),
// We need to retrieve an auth key from storage
flatMap(() => Observable.fromPromise(this._auth.getAuthorizationToken(Environment.ApiKey))),
// Now call the service that gets the addt. user data.
flatMap(key => this._profile.getCurrentProfile(`${Environment.Endpoints.Users}`, key)),
// Send it to the success action.
map(profile => {
console.log(profile);
return new ProfileGetSuccess({});
})
);
Reducer:
export function reducer(state = initialState, action: Actions): State
{
switch (action.type) {
case AuthActionTypes.REVALIDATE_SUCCESS:
console.log('REVALIDATE_SUCCESS');
return {
...state,
isAuthenticated: true,
profile: action.payload.profile,
onboarded: action.payload.onboarded
};
case AuthActionTypes.PROFILE_GET_SUCCESS:
console.log('PROFILE_GET_SUCCESS');
return { ...state, profile: action.payload.profile };
case AuthActionTypes.INVALIDATE_SUCCESS:
return { ...state, isAuthenticated: false, profile: undefined };
default:
return state;
}
}
As the title mentions, dispatching the action runs infinitely. Can anyone point me in the right direction?
The answer lies here:
this._profile.currentProfile.map needed to be this._profile.currentProfile.take(1).map. The issue wasn't the fact that all my actions were being called, but because I was running an action on an observable, I suppose it was re-running the action every time someone was touching the observable, which happened to be infinite times.
Moreso, I was able to refactor my action store so that I can get rid of my other actions to call to get the rest of the user's data, instead subscribing to this._profile.currentProfile and calling a non-effect based action, ProfileSet, when the observable's value changed. This let me remove 6 actions (since they were async calls and needed success/fail companion actions) so it was a pretty big win.

restarting a queue of API requests if a token refresh happened

I'm having a hard time wrapping my brain around this pattern I am trying to implement so I'm hoping the stack overflow community might be able to help me work through a solution to this.
Currently I use redux-thunk along with superagent to handle calls to me API and syncing it all up with redux
An example of this might look like
export const getUser = (id) => {
return (dispatch) => {
const deferred = new Promise((resolve, reject) => {
const call = () => {
API.get(`/users/${id}`)
.then((response) => response.body)
.then((response) => {
if (response.message === 'User found') {
serializeUser(response.data).then((response) => {
resolve(response);
});
} else {
reject('not found');
}
}).catch((err) => {
handleCatch(err, dispatch).then(call).catch(reject)
});
}
call()
});
return deferred;
};
};
In the case where the server comes back with a 200 and some data I continue on with putting the data into the store and rendering to the page or whatever my application does.
In the case I receive an error I have attempted to write a function that will intercept those and determine if it should show an error on page or in the case of a 401 from our API, attempt a token refresh and then try to recall the method...
import { refreshToken } from '../actions/authentication';
export default (err, dispatch) => {
const deferred = new Promise((resolve, reject) => {
if (err.status === 401) {
dispatch(refreshToken()).then(resolve).catch(reject)
} else {
reject(err);
}
})
return deferred;
};
This works, however, I have to add this to each call, and it doesn't account for concurrent calls that should not attempt to call if there is a refresh in progress.
I've seen some things in my research on this topic that maybe redux-saga could work but I haven't been able to wrap my brain around how I might make this work
Basically, I need something like a queue that all my API requests will go into that is maybe debounced so any concurrent requests will just be pushed to the end and once a timeout ends the calls get stacked up, when the first call gets a 401 it pauses the queue until the token refresh either comes back successful, in which case it continues the queue, or with a failure, in which case it cancels all future requests from the queue and sends the user back to a login page
The thing I would be worried about here is if the first call in the stack takes a long time, I don't want the other calls to then have to wait a long time because it will increase the perceived loading time to the user
Is there a better way to handle keeping tokens refreshed?

How I can do a job asynchronously using Promises in React Native?

In a React Native project, I wrote this function using Promise to do a job asynchronously;
function doEncryptionAsync(params) {
return new Promise(
function (resolve, reject) {
// Async code started
console.log('Promise started (Async code started)');
// The job that takes some times to process
var encrypted_value = new EncryptedValue(params);
if (true) {
resolveencrypted_value
}
else {
reject("Error while encrypting!");
}
}
)
}
And I call that in my Redux action;
export const encrypt = ( params ) => {
return (dispatch) => {
dispatch({
type: type.ENCRYPT
});
// Sync code started
console.log('Started (Sync code started)');
doEncryptionAsync(params)
.then((response) => {
// Async code terminated
console.log('Promise fulfilled (Async code terminated)');
encryptSuccess(dispatch, response);
})
.catch((error) => {
console.log(error);
encryptFail(dispatch);
});
// Sync code terminated
console.log('Promise made (Sync code terminated)');
}
}
It works, but not asynchronously! My main thread seems to be blocked until doEncryptionAsync() returns. The line console.log('Promise made (Sync code terminated)') runs, but not immediately!
My output for logs is like this;
// OUTPUT Simulation
Started (Sync code started) at time x
Promise started (Async code started) at time x
Promise made (Sync code terminated) at time (x + 2sec)
Promise fulfilled (Async code terminated) at time (x + 2sec)
My question is what's wrong with my approach to implement a AsyncTask?!
JavaScript's asynchronous behavior is only relevant for IO blocking functions. Meaning, that instead of waiting for an IO function, the event loop keeps running.
Seeing as JS is single threaded, CPU bounded computations take up the thread, and cannot be done asynchronously.
Your only recourse, then, is to create a native module that will do the calculation in a different thread for you, and then call a JS callback when it's done.

Resources