Skip to content

Add docs for testing Redux with hooks (e.g. useSelector, useDispatch, etc) and React Router and ++ #570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
kimfucious opened this issue Aug 20, 2020 · 19 comments

Comments

@kimfucious
Copy link

Is your feature request related to a problem? Please describe.
First off, I should say that this is an excellent testing library, and I do appreciate all the work that has gone into it.

That said, I'm always frustrated when I try to use React Testing Library on a project where I'm using the following setup:

  • Create React App
  • Redux, using combined reducers and Thunk middleware
  • State in all components is being accessed via useSelector hook (not using connect anymore)
  • Actions being dispatched with useDispatch hook

I start to try and write tests, and them I'm like, "Oh, yeah... this is why I'm always frustrated."

Then I read this, and try to think, "Ugh, I'm not using a single reducer, and I ain't got no initialState either."

So then I try to jam rootReducer in to see what will happen, and then, and then, and then it's a trip down a Google rabbit-hole and a couple hours of frustration, and then I give up.

Describe the solution you'd like
I'd love some docs that covered testing an app that implements Redux, using combined reducers and hooks (e.g. useSelector & useDispatch).

🤯 Even better would be a "kitchen sink" example that did everything, including Redux and React Router together with Thunk in Action Creators, and, and, and, ++.

I realize that this probably comes off as a bit whiney, but it's kinda meant to sound like that, because I'm like a kid that wants to play with a toy that's beyond his age range.

@eps1lon
Copy link
Member

eps1lon commented Aug 20, 2020

I feel like the redux docs are a better place for that. Otherwise we have to maintain these pages regarding testing-library and redux. Since they already have a pretty extensive testing guide I'd recommend opening an issue over there if you have questions.

We should probably just remove our example and link to their docs.

@kimfucious
Copy link
Author

I wrote something here, but it dissapeared...

Anyhow, I've read this, and it could turn out to be a case of "not my job" on either side.

My hope is to get some docs to work with, either way.

@lucasff
Copy link

lucasff commented Aug 26, 2020

hello! I'd kindly request to have documentation on how to test properly using React Context with the useContext hook.
I've been struggling with cryptic error messages, such as:

TypeError: Cannot read property "contextTypes" of undefined.
Using enzyme, for example, the documentation is not very clear on how to give context value to the Component as well.

@kentcdodds
Copy link
Member

it could turn out to be a case of "not my job" on either side.

None of us is getting paid to do this open source work so technically this is just as much your job as it is anyone else's 😉 That's open source for you 😅

@lucasff, googling "Test React Context" turns up this page in the docs: https://testing-library.com/docs/example-react-context

@kimfucious
Copy link
Author

Hi @kentcdodds,

Thanks for chiming in. Your snark is duely noted 🌮.

You may have interpreted my use of "not my job" to be that I expect this to be done for me, or that I don't appreciate/understand open source. My apologies, if that was the case.

In fact, I meant it to illustrate that--because this project is open source--I am hopeful that someone in the community may have some creative advice and/or ideas toward a solution, which would be helpful toward supplementing the project's docs.

Regardless, in my reply that I'd lost and didn't send earlier, I'd intimated that I'd love to help write these docs, but that my understanding--at this time--is such that I can't wrap my head around how to implement a "kitchen sink" testing solution yet.

I've been Googling tither and yon, trying this and that, and taking notes as I go. But no luck or lightbulbs as of yet.

If I do wind up happening upon a solution or creating one myself, I'll be sure to share it here for consideration.

@eps1lon
Copy link
Member

eps1lon commented Aug 27, 2020

@kimfucious Maybe start by sharing what you find lacking in https://redux.js.org/recipes/writing-tests? It seems so much more extensive that I would just drop our guide or upstream some stuff.

@MatanBobi
Copy link
Member

@kimfucious Maybe start by sharing what you find lacking in https://redux.js.org/recipes/writing-tests? It seems so much more extensive that I would just drop our guide or upstream some stuff.

Basically I wrote both of the docs here and there, the problem with dropping our guide is that if for some reason redux will decide to drop RTL explanation, it will leave us with no explanation at all.
I do think that having a thorough explanation and example containing all of the redux features should probably be on the redux docs :)

@eps1lon
Copy link
Member

eps1lon commented Aug 27, 2020

the problem with dropping our guide is that if for some reason redux will decide to drop RTL explanation, it will leave us with no explanation at all.

The same applies to any feature we add/drop or they add/drop. But if you think this is a likely scenario then why not just copy it as a whole?

Could we establish some communication with redux folks so sync on who should host these guides? Two guides is in my opinion strictly worse since now a reader has to decide which to follow. And if we contradict each other it gets really confusing.

@MatanBobi
Copy link
Member

Two guides is in my opinion strictly worse since now a reader has to decide which to follow. And if we contradict each other it gets really confusing.

I agree.
@markerikson, @timdorr - can we get your thoughts about this one please?

@markerikson
Copy link

I mean, we just merged your PR - why would we remove it? :)

@nickserv

This comment has been minimized.

@nickserv
Copy link
Member

nickserv commented Aug 29, 2020

@kimfucious I'll address some of your examples here. If you need more guided or in depth help approaching Redux/React/etc. tests, it may help to ask in the #redux channel on Reactiflux or the #react-testing-library channel on Testing Library, and then we can discuss additional doc updates here or in Redux's issues.

Redux, using combined reducers and Thunk middleware

As long as you use the real store (or a similar mock) in your tests this should be fine.

State in all components is being accessed via useSelector hook (not using connect anymore)

We can update the examples use use hooks, but it still shouldn't affect your test code as long as you use React Redux's Provider component as recommended.

Ugh, I'm not using a single reducer, and I ain't got no initialState either.

This should not affect your integration tests, only unit tests (which are explained in the Redux docs above Components).

I'd love some docs that covered testing an app that implements Redux, using combined reducers and hooks (e.g. useSelector & useDispatch).

The Redux docs are almost there, we'd just need to replace connect with hooks. Please let us know if you find anything else lacking though. I'd also recommend Testing JavaScript's React course (pro tier) for more in depth examples and integrations.

@nickserv
Copy link
Member

nickserv commented Aug 29, 2020

@eps1lon Two guides is in my opinion strictly worse since now a reader has to decide which to follow.

I agree, and having to maintain two similar articles isn't ideal. If we update Redux's Writing Tests guide to use hooks (without a separate unconnected component) it should be similar enough to the practices in the Testing Library recipe to replace it. Then Testing Library's recipe can link to Redux's article, which also has more in depth information about unit testing Redux code.

Either way we should use hooks in whatever docs we keep to be consistent with Redux's and React's recommendations for new connected components (unless @markerikson is already planning on something similar with the Redux docs rework).

@kimfucious
Copy link
Author

Hello Gents,

I'm impressed by and very appreciative of the positive attention that this thread has received from each of you.

My two cents regarding React Testing Library docs is that the docs should focus on how to make the product work in various scenarios, including combined scenarios.

Obviously there are a lot of different testing scenarios, so it's a big ask to think that everything would be covered, so focusing on the most common scenarios seems to be the best way forward.

I agree that maintaining docs in multiple locations is a bug-bear, so if RTL can point to Redux docs and those docs give a clear picture on how to proceed, that's perfect. In some cases, I can see that Redux docs are mirroring the RTL docs, like here.

For me personally, on the current project I'm working on, I think I have a fairly common combination of the following:

Redux wrapped around React Router:

const store = configureStore();

ReactDOM.render(
  <Provider store={store}>
    <AppRouter />
  </Provider>,
  document.getElementById("root")
);

AppRouter is using "protected routes" that rely on Redux useSelector hooks to grab auth state and redirect accordingly, for example:

import React from "react";
import { useSelector } from "react-redux";
import { Route, Redirect, useLocation } from "react-router-dom";

const PrivateRoute = ({ children, ...rest }) => {
  const location = useLocation();
  const { username } = useSelector((state) => state.auth);
  const isAuthenticated = !!username;
  
  return (
    <Route
      {...rest}
      render={({ location }) =>
        isAuthenticated ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/",
              state: { from: location }
            }}
          />
        )
      }
    />
  );

I use Redux useSelector/useDispatch hooks over connect with mapStateToProps/mapDispatchToProps on any new project these days along with combinedReducers() as well in the store config.

I use React Router useHistory() hook to push/replace users to other pages, sometimes with location state.

Nearly all my action creators are "thunked" with the following pattern:

export const doSignOutCognitoUser = () => async (dispatch) => {
  dispatch({ type: "SIGN_OUT_START" });
  try {
    await Auth.signOut();
    dispatch({ type: "SIGN_OUT_SUCCESS" });
  } catch (e) {
    dispatch({ type: "SIGN_OUT_FAIL", payload: e });
    console.log(e);
  }
};

Some components use all of the above, for example:

let Forms = ({ isPortrait }) => {
  const dispatch = useDispatch();
  const history = useHistory();
  const location = useLocation();
  const [, width] = useWindowSize();
  const {
    auth: {
      attributes: { "custom:role": role }
    },
    form: formData,
    formality: {
      alert,
      selectedEvent,
      selectedForm,
      hasPrintError,
      isProcessing,
      formToUpload
    },
    user: { impersonating, cookiesAccepted }
  } = useSelector((state) => state);

  const [isSpinning, setIsSpinning] = useState(false);

  useEffect(() => {
    if (alert && alert.message.includes("subscription is not active")) {
      history.push("/profile");
    }
  }, [alert, history]);

  // pushes manager back to My Employees page when impersonation alert is dismissed
  useEffect(() => {
    if (role === "manager" && !impersonating.id) {
      history.push("/my-employees");
    }
  }, [history, impersonating, location, role]);

  useEffect(() => {
    if (width < 768) {
      dispatch({ type: "SET_SELECTED_FORM", payload: "" });
    } else {
      if (!selectedForm) {
        dispatch({ type: "SET_SELECTED_FORM", payload: "appDetails" });
      }
    }
  }, [dispatch, selectedForm, width]);
...

I hope that gives you an idea of what I'm working with. I'm not sure, but while it seems like a lot of things are in use here, it's not that uncommon a setup.

To be blunt, I've just not been able to find any set of docs that helps me wrap my mind around how to setup testing for this "kitchen sink" type of scenario. If it's out there, I just haven't found it yet.

Lastly, I am talking unit tests only, for example:

  • When the page loads, given this state, this element is displayed.
  • When a button is clicked, this dispatch is triggered
  • With a given state, this function should return this value
    -- etc.

Integration tests would be Cypress, and that's a whole nuther thing, which is not on topic.

Please do ask further question of me if needed, I'm happy to help with this in any way that I can.

@nickserv
Copy link
Member

nickserv commented Aug 31, 2020

the docs should focus on how to make the product work in various scenarios, including combined scenarios.

I agree that having integrated docs is important to show more real world usage, but as we mentioned above it's not ideal to maintain two very similar doc pages.

Obviously there are a lot of different testing scenarios, so it's a big ask to think that everything would be covered, so focusing on the most common scenarios seems to be the best way forward.

In my opinion the Redux Writing Tests page already does a decent job. If you have any specific issues with that please open an issue on Redux or check out the #redux channel on Reactiflux.

In some cases, I can see that Redux docs are mirroring the RTL docs, like here.

Yea, I think the format of the Redux docs provides more useful context in this case though. The Testing Library docs are more focused on API references and general usage.

[code blocks]

The Connected Components section covers integration testing with providers. As long as your store has auth, the authenticated routes should just work. You can fire actions or preload the store with invalid/missing auth to test authentication failure. As for React Router, this should also be fine as long as you either have the router in your imported component implementations or tests. If you have a lot of routers in tests, you can use the same custom render function that the Testing Library and Redux examples are already using for Redux's Provider but also adding your router.

To be blunt, I've just not been able to find any set of docs that helps me wrap my mind around how to setup testing for this "kitchen sink" type of scenario.

It sounds like you may be looking for something more like Testing JavaScript as it covers React-related testing topics other than Redux and Testing Library (such as React Router) step-by-step and in more detail. I believe Epic React will have more complex kitchen-sync testing examples as well. I'm still open to suggestions for Redux and Testing Library things missing from the examples though.

Lastly, I am talking unit tests only

I wouldn't recommend unit testing user interfaces, especially with React. Testing Library is specifically designed in favor of integration tests, so I think you'll have the best experience with it if you take more of an integration testing approach.

  • When the page loads, given this state, this element is displayed.

You can use a query with .toBeInTheDocument().

  • When a button is clicked, this dispatch is triggered

Dispatching an event generally updates something in your React app, and you can assert that update happens with Testing Library (using waitFor if it's asyncronous).

  • With a given state, this function should return this value

You generally wouldn't test what functions are doing in an integration test. But if you wanted to unit test it as a separate entity you could, or you could use spies to make sure the internals are working properly if you can't show the result for some reason (like if it's some logging service that the user can't see but your tests can't use the logging service).

Integration tests would be Cypress, and that's a whole nuther thing, which is not on topic.

Kent and I would generally recommend Testing Library for integration and Cypress for end to end, but you can do what works for you.

@alexkrolick
Copy link
Collaborator

Generally we have included recipes for 3rd party libraries in these scenarios:

  • roll over from early days, when no lib would have shown RTL because it was brand-new
  • when their own docs either aren't sufficient or we disagree with what they have documented (out of sync with best practices or overall philosophy)

The docs look pretty good on the Redux side. I'd say we should just link to them.

@nickserv
Copy link
Member

nickserv commented Sep 1, 2020

If you haven't seen it yet, that's what I did in #588.

Do you think it would also help to do this for other recipes in the long run?

@kimfucious
Copy link
Author

kimfucious commented Sep 2, 2020

I've created a repo to gather some examples together with a silly, little app that has (or will have) most of the functionality that I'm looking to test.

At present, I've gotten past the hurdle of creating a wrapper, to reduce boilerplate, that gets tests running with both Redux and React Router.

This is all obviously incomplete at the onset, and it's evident that I don't really know what I'm doing yet, but it's a starting point that might help both to illustrate what I'm trying to test and others who might find the examples helpful.

@kimfucious
Copy link
Author

Lastly, I am talking unit tests only

I wouldn't recommend unit testing user interfaces, especially with React. Testing Library is specifically designed in favor of integration tests, so I think you'll have the best experience with it if you take more of an integration testing approach.
...
Kent and I would generally recommend Testing Library for integration and Cypress for end to end, but you can do what works for you.

Thanks for this, @nickmccurdy.

I misused the term, "unit tests", in the above. I believe that I should have said 'integration tests".

You could verify the things I'm trying to test here, if you're at all interested.

@nickserv nickserv closed this as completed Nov 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants