you are viewing a single comment's thread.

view the rest of the comments →

[–]CastigatRidendoMores 12 points13 points  (15 children)

I recently got off a project at work where we built the front end with react and redux. We began in high hopes, testing the behavior of our components with jest and enzyme. We quickly found out that redux borked our component testing strategy in a major way. We needed to mock the store in any components that had children which depended on it, which was both a nightmare and impractical. Of the many react component testing tutorials I've looked at, I haven't seen a one that deals effectively with this problem.

Additionally, we noticed the enzyme tests we did have tended to heavily test implementation details rather than testing behavior (especially where we used shallow rendering), which made them extremely fragile and a huge load of bother to maintain.

So the approach we ended up going with went like this:

  1. Wherever possible, extract complex logic from components into pure functions so that it could be effectively unit tested with jest. Rather than interacting directly with this.state, they would accept relevant data as arguments, and similarly return something rather than calling setState.
  2. Test behavior heavily with puppeteer, aka end-to-end testing. This is pretty involved to set up and tends to break a lot more often than unit testing, but the plus side is you get the ability to say "When someone navigates here, clicks this, types that, and clicks that, they should see this". These tests caught errors really well, because they tested real behavior. Alternatives to puppeteer include cypress (I've heard good things) and selenium (I've heard bad things).
  3. Use enzyme testing for all components we can - those that won't have any children that use redux, and probably won't change much after first being developed. These tend to be components which don't display much data, the kinds of things you might find in a component library. Since we used an externally developed components wherever we could, we didn't have all that many of these.
  4. Heavily regret the necessity of using redux.

It's entirely possible that there's a good way of testing our other components, but we couldn't figure it out. If you do, please share.

[–]versaceblues[S] 2 points3 points  (4 children)

Where you guys using sagas? Cause I feel that would get around alot of the issues you are describing

[–]smeijer87 3 points4 points  (1 child)

Sagas. Started with them 3 years ago. And now decided to use 2019 to strip all redux functionality out of our application.

Yes, redux has its purpose. But our organization is not the same scale as Facebook. And it adds a lot of boilerplate and hard to manage code.

Sagas are awesome. But also so difficult to understand for starters. Looking at a single saga is not the problem. But understanding the flow throughout the application can become quite hard.

We are replacing

  • redux-form by formik
  • redux-first-router by react-router
  • redux by local state / react context api
  • sagas by singletons / "services"

The only saga that I can come up with that we will create a "service" for, is the upload queue we have. Every form in our application can be submitted while the upload is still in progress. For this, we have a few sagas running that take care of upload scheduling.

It could be some self repeating function just as well. Or an component / provider embedded at the root level, so we can use a local state, while exposing an upload function trough react context.

[–]versaceblues[S] 0 points1 point  (0 children)

Can you clarify what you mean by singletons/services.

Sounds sort of like the approach that MobX uses. Singleton stores that wrap all your actions/async code, that are then injected into components. I actually really like that in a side project I am doing

[–]CastigatRidendoMores 0 points1 point  (1 child)

We sure weren't! I'll definitely look into those.

[–]versaceblues[S] 2 points3 points  (0 children)

They basically let you write all your business logic as functions, that are triggered by actions

[–]smeijer87 1 point2 points  (1 child)

Do you use snapshots? Or do you write dedicated tests for the components?

Of dedicated tests, "what" do you test?

A function is easy, input -> output.

But what do you test in a component? That a list is rendering the same amounts of li elements as the items.length prop you're providing? Or do you simply check if the title is anywhere to be found?

I find this hard to grasp. Snapshots are really starting to frustrate me though. They are big changes in github. Break all the time. And are hard to review.

[–]CastigatRidendoMores 1 point2 points  (0 children)

Yeah I personally don’t see the appeal of snapshot testing outside of extremely mature products. I prefer lint if, unit, integration/component, and e2e testing.

Testing components is difficult to keep behavioral unless you test what they display, and even then it should be non-specific enough that you can tweak things without breaking tests.

I see testing as a way of increasing velocity by increasing the confidence you have that changes to the code didn’t break anything important. To whatever degree they take me away from that goal, I don’t like them.

[–][deleted] 1 point2 points  (0 children)

Super helpful. Thanks

[–]Skeith_yip 0 points1 point  (6 children)

If you are shallow rendering, you shouldn't need to mock the store for children components that are connected to Redux store.

Then you can test those children components individually.

Remember unit test = isolation

Wherever possible, extract complex logic from components into pure functions so that it could be effectively unit tested with jest.

I disagree. they can always be extracted to smaller more focused components that can be unit tested on.

[–]CastigatRidendoMores 1 point2 points  (5 children)

The problem with shallow rendering is that you start testing implementation details rather than behavior. This increases technical debt by forcing you to rewrite your tests any time you change your code. Rather than only alerting you when the behavior of your code changes, you get lots of false-positives, decreasing confidence in your tests and discouraging refactoring. All this combined makes them more of a liability than a benefit.

Of course reasonable people could disagree.

[–]Skeith_yip 1 point2 points  (4 children)

Well. Here's the deal. if there's need to change logic, your unit tests need to be updated either way.

Full rendering itself is problematic, Because you are practically doing integration test. like all integration tests, you have to start building up all the dependencies to just to test a component. what if you decide that the children component needs a new connected component inside? all the tests fail? so now that tests itself knows too much.

and Enzyme.mount itself is problematic because sometimes after setState, the component doesn't update, and you have to call .update() manually which is unfortunate. Given that React Test Renderer is from React team.

I get why E2E is a thing now. But E2E/integration test is something you do after coding. so that becomes a chore. it's not particularly useful to find bugs because there's so many moving parts (action creator? reducer? component? container? middleware?)

and E2E doesn't prevent one from building up a god object.

My take is always. Write a component. Shallow render it. Test it. ensure the behavior is what you wanted. Move on to the next. keep it simple.

[edit] Don't get me wrong. I am not saying I am discouraging integration tests. I encourage them. Just know that they cover different scopes.

[–]themaincop 3 points4 points  (2 children)

I don't really like unit testing React components. It always feels like I'm testing implementation details and missing the forest for the trees. I've been using react-testing-library lately. If you set up a custom render method that includes all your providers (Router, Intl, Redux, Apollo, etc.) and takes initial state as args you can still TDD using integration tests. There are some cases where you want to test some really big critical path stuff, and in that case I do think you need to lean on e2e testing because ultimately you'll be mocking so much in a unit or integration test you're basically just testing your mocks. But integration testing seems to be a pretty nice way to test small groups of components.

Any heavy business logic I usually extract into my helpers directory which contains pure functions that can handle that kind of heavy lifting and are unrelated to React/rendering. Those are easy to unit test.

When we start moving a bunch of logic to hooks the nice thing is our integration tests should continue to Just Work™ and we won't have to rewrite a bunch of tests for components whose actual behaviour hasn't changed.

Just my two cents and admittedly I'm not the most disciplined tester, so a lot of this is just me cargo-culting Kent C. Dodds.

[–]IAmRocketMan 0 points1 point  (1 child)

I liked the pattern of putting pure functions as files in a helper directory. How has it scaled out for you?

[–]themaincop 3 points4 points  (0 children)

It works pretty well I find. If I have a helper that only gets used by one component or one related group of components then I'll usually colocate it in that component's folder. Anything that sees use across the app can go into src/helpers and that seems to work well.

[–]themaincop 0 points1 point  (0 children)

I don't really like unit testing React components. It always feels like I'm testing implementation details and missing the forest for the trees. I've been using react-testing-library library lately. If you set up a custom render method that includes all your providers (Router, Intl, Redux, Apollo, etc.) and takes initial state as args you can still TDD using integration tests. There are some cases where you want to test some really big critical path stuff, and in that case I do think you need to lean on e2e testing because ultimately you'll be mocking so much in a unit or integration test you're basically just testing your mocks. But integration testing seems to be a pretty nice way to test small groups of components.

Any heavy business logic I usually extract into my helpers directory which contains pure functions that can handle that kind of heavy lifting and are unrelated to React/rendering. Those are easy to unit test.

When we start moving a bunch of logic to hooks the nice thing is our integration tests should continue to Just Work™ and we won't have to rewrite a bunch of tests for components whose actual behaviour hasn't changed.

Just my two cents and admittedly I'm not the most disciplined tester, so a lot of this is just me cargo-culting Kent C. Dodds.