-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore(deps): install twitter embed * post(goodbye-enzyme): initial content * post(goodbye-enzyme): add content * post(goodbye-enzyme): add content + update banner * post(goodbye-enzyme): update post date * post(goodbye-enzyme): update post content
- Loading branch information
1 parent
b8c2e70
commit f9fca4d
Showing
4 changed files
with
713 additions
and
307 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
--- | ||
title: "Goodbye Enzyme" | ||
description: "You won't be missed..." | ||
pubDate: "2024-04-19" | ||
tags: | ||
[ | ||
"frontend", | ||
"javascript", | ||
"typescript", | ||
"react", | ||
"enzyme", | ||
"react testing library", | ||
"testing", | ||
] | ||
--- | ||
|
||
<style> | ||
{`.twitter-tweet { | ||
margin-left: auto; | ||
margin-right: auto; | ||
`} | ||
</style> | ||
|
||
import { Tweet } from "@astro-community/astro-embed-twitter"; | ||
import Banner from "../../../../components/Banner/index.astro"; | ||
|
||
I've showed my age in the [previous post](./snapshot-testing-sucks.md), so I won't do it again. But, for the | ||
purposes of this post, I will say that I've been a professional in the industry prior to [Enzyme](https://enzymejs.github.io/enzyme/), | ||
during its height, and after it (i.e. now). | ||
|
||
This leads me to say, using my _expert_ opinion, "Goodbye Enzyme, and good riddance. You will not be missed." | ||
|
||
That may seem harsh, but I stand behind it. I've recently removed Enzyme from multiple, highly used codebases at my work | ||
and have see the horrors caused by Enzyme. I'll cover some of these in this article, comparing it to [React Testing Library][rtl]. | ||
|
||
## Enzyme? React Testing Library (RTL)? What are they? | ||
|
||
They are both testing frameworks aiming to make it easier to test React components. They both have their own philosophies | ||
and ways of doing things. | ||
|
||
### Enzyme | ||
|
||
> Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components' output. | ||
> You can also manipulate, traverse, and in some ways simulate runtime given the output. | ||
### RTL | ||
|
||
> You want to write maintainable tests that give you high confidence that your components are working for your users. | ||
> As a part of this goal, you want your tests to avoid including implementation details so refactors of your components | ||
> (changes to implementation but not functionality) don't break your tests and slow you and your team down. | ||
<Tweet id="https://twitter.com/kentcdodds/status/977018512689455106" /> | ||
|
||
These differing philosophies are the main reason why I prefer RTL over Enzyme. Enzyme is more focused on the "output" of | ||
a component. Additionally, they talk about traversing, manipulating, and simulating the output, which does not align | ||
with how actual users would be interacting with it. | ||
|
||
On the other hand, RTL is more focused on the "functionality" of a component. It is more concerned with how a user would | ||
interact with the component, and how the component would respond to that interaction. ❤️ | ||
|
||
<Banner type="info"> | ||
I'm going to use personal anecdote. These may not match your experience. | ||
</Banner> | ||
|
||
## Problems with Enzyme | ||
|
||
### Testing async scenarios is difficult | ||
|
||
One of the biggest issues I've had with Enzyme is testing async scenarios. Unless you're purely working on a presentational component, most components will have some form of async natur. Something like data loading, form input and validation, etc. | ||
|
||
You'll need to perform an action, wait for the async action to complete, then move on with your test. | ||
|
||
This is a nightmare to do in Enzyme. Code using `setTimeout`, `setInterval`, `setImmediate`, `await Promise.resolve()` or similar is likely trying to account for these async actions. It's HACKY... and I never liked doing it, even though there was no other way. | ||
|
||
RTL handles this much better in two ways, maybe more. | ||
|
||
First, RTL provides a `waitFor` utility. It allows you to wait for a condition to be true before moving on with the test. This can be used in a sync or async manner. Using it in an async manner will ensure that the async action has completed before moving on. | ||
|
||
Second, the [`@testing-library/user-event` package](https://testing-library.com/docs/user-event/intro/) provides utility methods to help deal with user interactions. When a user interaction triggers an async action via a command like `click`, `type`, `clear`, etc., the command can be awaited to ensure the async action has completed. | ||
|
||
This functionality is only in the newer versions of `user-event`, but it's already proved amazingly helpful. | ||
|
||
### Simulating interactions | ||
|
||
When testing user interactions, Enzyme simulates events in a programmatic way by dispatching DOM events. | ||
|
||
IMO, this doesn't mimic real user interaction as there may be multiple events that can fire in a single interaction. | ||
|
||
For example, how many events fire when clicking a button? Well, there's quite a few: | ||
|
||
1. `mouseover` | ||
2. `mousedown` | ||
3. `mouseup` | ||
4. `click` (interesting that this is after `mouseup`... hmmm haha) | ||
5. `mouseout` | ||
|
||
If you're only simulating a "click" you're missing out on a lot of the events that would happen during an interaction. This leads | ||
to less confidence in your tests (IMO). | ||
|
||
<Banner type="info"> | ||
Checkout my in-depth post about [Simulating | ||
Events](../../2023/04/simulating-js-events.mdx) for more context! | ||
</Banner> | ||
|
||
Or, if we take an example directly from their [website](https://testing-library.com/docs/user-event/intro/): | ||
|
||
> For example, when a user types into a text box, the element has to be focused, and then keyboard and input events are fired and the selection and value on the element are manipulated as they type. | ||
RTL, on the other hand, uses the `@testing-library/user-event` package to simulate user interactions (you can still use the | ||
`fireEvent` method though if you really want to). This takes into account the multitude events that could happen when a user | ||
interacts with a component. | ||
|
||
### Having access to props | ||
|
||
This is a killer... and I hate it. I've seen it abused so many times. | ||
|
||
You can see a basic form below. | ||
|
||
```js | ||
const Component = ({ onSubmit }) => { | ||
return ( | ||
<form onSubmit={onSubmit}> | ||
<input type="text" name="input1" /> | ||
<input type="text" name="input2" /> | ||
<button type="submit" onSubmit={onSubmit}> | ||
Submit | ||
</button> | ||
</form> | ||
); | ||
}; | ||
``` | ||
|
||
It's likely there's some form of validation on the inputs and once the form is submitted some action has to take place. | ||
|
||
In Enzyme, you can access the props of a component and call the `onSubmit` function directly, allowing something like: | ||
|
||
```js | ||
it("should submit form", () => { | ||
const someAsyncAction = jest.fn(); | ||
const onSubmit = () => { | ||
someAsyncAction(); | ||
}; | ||
const wrapper = shallow(<Component onSubmit={onSubmit} />); | ||
|
||
wrapper.find("form").props("onSubmit")(); | ||
|
||
expect(someAsyncAction).toHaveBeenCalled(); | ||
}); | ||
``` | ||
|
||
This is hard to look at. There's no user interaction and no validity checks. We're just calling a function directly (yes, | ||
I have seen this in real life). | ||
|
||
While this is a simple example, you can see how it would open the door for lazy testing (and it does). | ||
|
||
## /endrant | ||
|
||
While Enzyme was an absolute _killer_ when it first came out, enabling the testing of UI components like we'd not seen before, it's | ||
not fit for purpose anymore. | ||
|
||
It actively encourages bad practices, can't be used past v16 of React (with an official adapter), | ||
isn't maintained, and has a philosophy that doesn't align with how users interact with components. | ||
|
||
Please, please consider using [RTL][rtl] for your testing needs. It's a much better framework that encourages better testing. | ||
|
||
Goodbye Enzyme, and good riddance. You will not be missed. | ||
|
||
<script async src="https://platform.twitter.com/widgets.js"></script> | ||
|
||
[rtl]: (https://testing-library.com/docs/react-testing-library/intro/). |
Oops, something went wrong.