Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Complex Asynchronous Redux Actions

So, let's say I have the next action:

export function login({ email, password, redirectTo, doNotRedirect }) {
  return ({ dispatch }) => {
    const getPromise = async () => {
      const basicToken = Base64.encode(`${email}:${password}`);
      const authHeaders = { Authorization: `Basic ${basicToken}` };
      const { payload, error } = await dispatch(sendAuthentication(authHeaders));

      if (error) throw payload;

      const { username, token, fromTemporaryPassword } = payload;
      const encodedToken = Base64.encode(`${username}:${token}`);

      dispatch(persistence.set('authorizationToken', encodedToken));
      dispatch(postGlobalId({ username }));
      dispatch(setIsLoggedIn(true));
      dispatch(setIsFromTemporaryPassword(fromTemporaryPassword));

      await dispatch(clientActions.fetchClient);

      if (doNotRedirect) return;

      if (fromTemporaryPassword)
        dispatch(updatePath('/profile/change-password'));
      else
        dispatch(updatePath(redirectTo || '/dashboard'));
    };

    return {
      type: AUTHENTICATION_LOGIN,
      payload: getPromise()
    };
  };
}

And I want to add tests for it, to add reliability to the code.

So, here are few things:

  1. We send authentication headers and get data as a response
  2. We throw an error if some error is present in the response
  3. We set up all needed tokens, dispatch all needed actions to show that we are logged in now
  4. Fetching client data
  5. Based on params and received data, we redirect to needed route / don't redirect

The question is that it is really too hard to test and we need to stub literally everything, which is bad due to brittle tests, fragility and too much of implementation knowing (not to mention that it is pretty challenging to stub dispatch to work properly).

Therefore, should I test all of these 5 points, or to focus only on the most important stuff, like sending authorization request, throw error and check redirects? I mean, the problem with all flags that they can be changed, so it is not that reliable.

Another solution is just to separate these activities into something like following:

  • auth
  • setLoginInfo
  • handleRedirects

And to pass all needed functions to invoke through dependency injection (here just with params, basically)? With this approach I can spy only invoking of this functions, without going into much details.

I am quite comfortable with unit testing of pure functions and handling different edge-cases for them (without testing too much implementation, just the result), but testing complex functions with side-effects is really hard for me.

like image 946
Bloomca Avatar asked Jul 02 '26 19:07

Bloomca


1 Answers

If you have very complex actions like that, I think an alternative (better?) approach is to have simple synchronous actions instead (you can even just dispatch payloads directly, and drop action creators if you like, reducing boiler-plate), and handle the asynchronous side using redux-saga: https://github.com/yelouafi/redux-saga

Redux Saga makes it very simple to factor out your business logic code into multiple simple generator functions that can be tested in isolation. They can also be tested without the underlying API methods even being called, due to the 'call' function in that library: http://yelouafi.github.io/redux-saga/docs/api/index.html#callfn-args. Due to the use of generators, your test can 'feed' values to the saga using the standard iterator.next method. Finally, they make it much easier for reducers to have their say, since you can check something from store state (e.g. using a selector) to see what to do next in your saga.

If Redux + Redux Saga had existed before I started on my app (about 100,000 JS(X) LOC so far), I would definitely have used them.

like image 123
TomW Avatar answered Jul 05 '26 08:07

TomW



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!