惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
云风的 BLOG
云风的 BLOG
G
GRAHAM CLULEY
Spread Privacy
Spread Privacy
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
Arctic Wolf
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
P
Privacy International News Feed
Blog — PlanetScale
Blog — PlanetScale
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
The Register - Security
The Register - Security
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
Recent Announcements
Recent Announcements
Martin Fowler
Martin Fowler
A
About on SuperTechFans
W
WeLiveSecurity
GbyAI
GbyAI
V
Vulnerabilities – Threatpost
The GitHub Blog
The GitHub Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
Y
Y Combinator Blog
月光博客
月光博客
Scott Helme
Scott Helme
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
U
Unit 42
G
Google Developers Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threatpost
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google Online Security Blog
Google Online Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 司徒正美

Kent C. Dodds Blog

Implementing Hybrid Semantic + Lexical Search Simplifying Containers with Cloudflare Sandboxes Migrating to Workspaces and Nx Offloading FFmpeg with Cloudflare Building Semantic Search on my Content Helping YOU ask ME questions with AI How I used Cursor to Migrate Frameworks The Dow's Start on the Covenant Path 2025 in Review The next chapter: EpicAI.pro AI is taking your job How I increased my visibility Launching Epic Web 2023 in Review Stop Being a Junior RSC with Dan Abramov and Joe Savona Live Stream Fixing a Memory Leak in a Production Node.js App 2022 in Review My Car Accident I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS I'm building EpicWeb.dev A review of my time at Remix Remix: The Yang to React's Yin How I help you build better websites Why I Love Remix The State Initializer Pattern How to React ⚛️ Get a catch block error message with TypeScript Building an awesome image loading experience How Remix makes CSS clashes predictable Introducing the new kentcdodds.com How I built a modern website in 2021 How to use React Context effectively Static vs Unit vs Integration vs E2E Testing for Frontend Apps The Testing Trophy and Testing Classifications Array reduce vs chaining vs for loop Don't Solve Problems, Eliminate Them Super Simple Start to Remix Super Simple Start to ESModules in Node.js JavaScript Pass By Value Function Parameters How to write a Constrained Identity Function (CIF) in TypeScript How to optimize your context value How to write a React Component in TypeScript TypeScript Function Syntaxes Listify a JavaScript Array Build vs Buy: Component Libraries edition Using fetch with TypeScript Wrapping React.useState with TypeScript Define function overload types with TypeScript 2020 in Review Business and Engineering alignment Hi, thanks for reaching out to me 👋 useEffect vs useLayoutEffect Super simple start to Firebase functions Super simple start to Netlify functions Super Simple Start to css variables Favor Progress Over Pride in Open Source Testing Implementation Details How getting into Open Source has been awesome for me useState lazy initialization and function updates Use ternaries rather than && in JSX Application State Management with React Use react-error-boundary to handle errors in React JavaScript to Know for React How I structure Express apps What open source project should I contribute to? When I follow TDD AHA Programming 💡 How I Record Educational Videos Should I write a test or fix a bug? Stop mocking fetch Intentional Career Building Improve test error messages of your abstractions Tracing user interactions with React Eliminate an entire category of bugs with a few simple tools Common mistakes with React Testing Library Super Simple Start to React Stop using client-side route redirects The State Reducer Pattern with React Hooks Function forms Replace axios with a simple custom fetch wrapper How to test custom React hooks React Production Performance Monitoring Should I useState or useReducer? Stop using isLoading booleans Make Your Test Fail Make your own DevTools An Argument for Automation Fix the "not wrapped in act(...)" warning Super Simple Start to ESModules in the Browser Implementing a simple state machine library in JavaScript 2010s Decade in Review Why users care about how you write code Why I avoid nesting closures Don't call a React function component Why your team needs TestingJavaScript.com Inversion of Control Understanding React's key prop How to Enable React Concurrent Mode Profile a React App for Performance
Why I Never Use Shallow Rendering
2018-07-23 · via Kent C. Dodds Blog

I remember a few years ago when I got started with React I decided I needed to figure out how to test React components. I tried shallow from enzyme and immediately decided that I would never use it to test my React components. I've expressed this feeling on many occasions and get asked on a regular basis why I feel the way I do about shallow rendering and why React Testing Library will never support shallow rendering.

So finally I'm coming out with it and explaining why I never use shallow rendering and why I think nobody else should either. Here's my main assertion:

With shallow rendering, I can refactor my component's implementation and my tests break. With shallow rendering, I can break my application and my tests say everything's still working.

This is highly concerning to me because not only does it make testing frustrating, but it also lulls you into a false sense of security. The reason I write tests is to be confident that my application works and there are far better ways to do that than shallow rendering.

What even is shallow rendering?

For the purposes of this article, let's use this example as our subject under test:

import * as React from 'react'
import { CSSTransition } from 'react-transition-group'

function Fade({ children, ...props }) {
	return (
		<CSSTransition {...props} timeout={1000} className="fade">
			{children}
		</CSSTransition>
	)
}

class HiddenMessage extends React.Component {
	static defaultProps = { initialShow: false }
	state = { show: this.props.initialShow }
	toggle = () => {
		this.setState(({ show }) => ({ show: !show }))
	}
	render() {
		return (
			<div>
				<button onClick={this.toggle}>Toggle</button>
				<Fade in={this.state.show}>
					<div>Hello world</div>
				</Fade>
			</div>
		)
	}
}

export { HiddenMessage }

Here's an example of a test that uses shallow rendering with enzyme:

import * as React from 'react'
import Enzyme, { shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import { HiddenMessage } from '../hidden-message'

Enzyme.configure({ adapter: new Adapter() })

test('shallow', () => {
	const wrapper = shallow(<HiddenMessage initialShow={true} />)
	expect(wrapper.find('Fade').props()).toEqual({
		in: true,
		children: <div>Hello world</div>,
	})
	wrapper.find('button').simulate('click')
	expect(wrapper.find('Fade').props()).toEqual({
		in: false,
		children: <div>Hello world</div>,
	})
})

To understand shallow rendering, let's add a console.log(wrapper.debug()) which will log out the structure of what enzyme has rendered for us:

<div>
	<button onClick={[Function]}>Toggle</button>
	<Fade in={true}>
		<div>Hello world</div>
	</Fade>
</div>

You'll notice that it's not actually showing the CSSTransition which is what Fade is rendering. This is because instead of actually rendering components and calling into the Fade component, shallow just looks at the props that would be applied to the React elements created by the component you're shallowly rendering. In fact, if I were to take the render method of the HiddenMessage component and console.log what it's returning, I'd get something that looks a bit like this:

{
  "type": "div",
  "props": {
    "children": [
      {
        "type": "button",
        "props": {
          "onClick": [Function],
          "children": "Toggle"
        }
      },
      {
        "type": [Function: Fade],
        "props": {
          "in": true,
          "children": {
            "type": "div",
            "props": {
              "children": "Hello world"
            }
          }
        }
      }
    ]
  }
}

Look familiar? So all shallow rendering is doing is taking the result of the given component's render method (which will be a React element (read What is JSX?)) and giving us a wrapper object with some utilities for traversing this JavaScript object. This means it doesn't run lifecycle methods (because we just have the React elements to deal with), it doesn't allow you to actually interact with DOM elements (because nothing's actually rendered), and it doesn't actually attempt to get the react elements that are returned by your custom components (like our Fade component).

Why people use shallow rendering

When I determined early on to never use shallow rendering, it was because I knew that there were better ways to get at the things that shallow rendering makes easy without making the trade-offs shallow rendering forces you to make. I recently asked folks to tell me why they use shallow rendering. Here are a few of the things that shallow rendering makes easier:

  1. ... for calling methods in React components
  2. ... it seems like a waste to render all of the children of each component under test, for every test, hundreds/thousands of times...
  3. For actual unit testing. Testing composed components introduces new dependencies that might trigger an error while the unit itself might still work as intended.

There were more responses, but these sum up the main arguments for using shallow rendering. Let's address each of these:

Calling methods in react components

Have you ever seen or written a test that looks like this?

test('toggle toggles the state of show', () => {
	const wrapper = shallow(<HiddenMessage initialShow={true} />)
	expect(wrapper.state().show).toBe(true) // initialized properly
	wrapper.instance().toggle()
	wrapper.update()
	expect(wrapper.state().show).toBe(false) // toggled
})

This is a great reason to use shallow rendering, but it's a really poor testing practice. There are two really important things that I try to consider when testing:

  1. Will this test break when there's a mistake that would break the component in production?
  2. Will this test continue to work when there's a fully backward compatible refactor of the component?

This kind of test fails both of those considerations:

  1. I could mistakenly set onClick of the button to this.tgogle instead of this.toggle. My test continues to work, but my component is broken.
  2. I could rename toggle to handleButtonClick (and update the corresponding onClick reference). My test breaks despite this being a refactor.

The reason this kind of test fails those considerations is because it's testing irrelevant implementation details. The user doesn't care one bit what things are called. In fact, that test doesn't even verify that the message is hidden properly when the show state is false or shown when the show state is true. So not only does the test not do a great job keeping us safe from breakages, it's also flakey and doesn't actually test the reason the component exists in the first place.

In summary, if your test uses instance() or state(), know that you're testing things that the user couldn't possibly know about or even care about, which will take your tests further from giving you confidence that things will work when your user uses them.

... it seems like a waste ...

There's no getting around the fact that shallow rendering is faster than any other form of testing react components. It's certainly way faster than mounting a react component. But we're talking a handful of milliseconds here. Yes, it will add up, but I'd gladly wait an extra few seconds or minutes for my tests to finish in exchange for my tests actually giving me confidence that my application will work when I ship it to users.

In addition to this, you should probably use Jest's capabilities for only running tests relevant to your changes while developing your tests so the difference wont be perceivable when running the test suite locally.

For actual unit testing

This is a very common misconception: "To unit test a react component you must use shallow rendering so other components are not rendered." It's true that shallow rendering doesn't render other components (as demonstrated above), what's wrong with this is that it's way too heavy handed because it doesn't render any other components. You don't get a choice.

Not only does shallow rendering not render third party components, it doesn't even render in-file components. For example, the <Fade /> component we have above is an implementation detail of the <HiddenMessage /> component, but because we're shallow rendering <Fade /> isn't rendered so changes to that component could break our application but not our test. That's a major issue in my mind and is evidence to me that we're testing implementation details.

In addition, you can definitely unit test react components without shallow rendering. Checkout the section near the end for an example of such a test (uses React Testing Library, but you could do this with enzyme as well) that uses Jest mocking to mock out the <CSSTransition /> component.

I should add that I generally am against mocking even third party components 100% of the time. The argument for mocking third party components I often hear is Testing composed components introduces new dependencies that might trigger an error while the unit itself might still work as intended.. But isn't the point of testing to be confident the application works? Who cares if your unit works if the app is broken? I definitely want to know if the third party component I'm using breaks my use case. I mean, I'm not going to rewrite their entire test base, but if I can easily test my use case by not mocking out their component then why not do that and get the extra confidence?

I should also add that I'm in favor of relying more heavily on integration testing. When you do this, you need to unit test fewer of your simple components and wind up only having to unit test edge cases for components (which can mock all they want). But even in these situations, I still think it leads to more confidence and a more maintainable testbase when you're explicit about which components are being mocked and which are being rendered by doing full mounting and explicit mocks.

Without shallow rendering

I'm a huge believer of the guiding principle of React Testing Library:

The more your tests resemble the way your software is used, the more confidence they can give you.  — Kent C. Dodds 👋

That's why I wrote the library in the first place. As a side-note to this shallow rendering post, I want to mention there are fewer ways for you to do things that are impossible for the user to do. Here's the list of things that React Testing Library cannot do (out of the box):

  1. shallow rendering
  2. Static rendering (like enzyme's render function).
  3. Pretty much most of enzyme's methods to query elements (like find) which include the ability to find by a component class or even its displayName (again, the user does not care what your component is called and neither should your test). Note: React Testing Library supports querying for elements in ways that encourage accessibility in your components and more maintainable tests.
  4. Getting a component instance (like enzyme's instance)
  5. Getting and setting a component's props (props())
  6. Getting and setting a component's state (state())

All of these things are things which users of your component cannot do, so your tests shouldn't do them either. Below is a test of the <HiddenMessage /> component which resembles the way a user would use your component much more closely. In addition, it can verify that you're using <CSSTransition /> properly (something the shallow rendering example was incapable of doing).

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { CSSTransition } from 'react-transition-group'

import { HiddenMessage } from '../hidden-message'

// NOTE: you do NOT have to do this in every test.
// Learn more about Jest's __mocks__ directory:
// https://jestjs.io/docs/en/manual-mocks
jest.mock('react-transition-group', () => {
	return {
		CSSTransition: jest.fn(({ children, in: show }) =>
			show ? children : null,
		),
	}
})

test('you can mock things with jest.mock', () => {
	render(<HiddenMessage initialShow={true} />)
	const toggleButton = screen.getByText(/toggle/i)

	const context = expect.any(Object)
	const children = expect.any(Object)
	const defaultProps = { children, timeout: 1000, className: 'fade' }

	expect(CSSTransition).toHaveBeenCalledWith(
		{ in: true, ...defaultProps },
		context,
	)
	expect(screen.getByText(/hello world/i)).not.toBeNull()

	CSSTransition.mockClear()

	userEvent.click(toggleButton)

	expect(screen.queryByText(/hello world/i)).toBeNull()
	expect(CSSTransition).toHaveBeenCalledWith(
		{ in: false, ...defaultProps },
		context,
	)
})

Conclusion

A few weeks ago, my DevTipsWithKent (my weekdaily livestream on YouTube) I livestreamed "Migrating from shallow rendering react components to explicit component mocks". In that I demonstrate some of the pitfalls of shallow rendering I describe above as well as how to use jest mocking instead.

I hope this is helpful! We're all just trying our best to deliver an awesome experience to users. I wish you luck in that endeavor!

P.S.

Someone brought this up:

Shallow wrapper is good to test small independent components. With proper serializer it allows to have clear and understandable snapshots.

I very rarely use snapshot testing with react and I certainly wouldn't use it with shallow. That's a recipe for implementation details. The whole snapshot is nothing but implementation details (it's full of component and prop names that change all the time on refactors). It'll fail any time you touch the component and the git diff for the snapshot will look almost identical to the one for your changes to the component.

This will make people careless about changes to the snapshot updates because they change all the time. So it's basically worthless (almost worse than no tests because it makes you think you're covered when you're not and you won't write proper tests because they're in place).

I do think that snapshots can be useful though. For more about this from me, checkout another blog post:

Effective Snapshot Testing

I hope that helps!