|
1 | 1 | # Break JSDOM
|
2 | 2 |
|
3 |
| -0. You have a tiny React app with Vite. |
4 |
| -1. Install Vitest and JSDOM, and write a basic integration test for your React component. |
5 |
| -1. Run it to see it _failing_ even on this simple test. |
| 3 | +I start with rendering the `<FilePreview />` component in my test: |
| 4 | + |
| 5 | +```tsx add=1,4 |
| 6 | +import { test, expect } from 'vitest' |
| 7 | +import { render, screen } from '@testing-library/react' |
| 8 | +import { FilePreview } from './file-review.js' |
| 9 | + |
| 10 | +test('displays the preview card', () => { |
| 11 | + render(<FilePreview file={new File(['hello world'], 'file.txt')} />) |
| 12 | +}) |
| 13 | +``` |
| 14 | + |
| 15 | +I am using the `render()` function from React Testing Library as usual. Since my component requires the `file` prop, I am creating a dummy file instance using the [`File` API](https://developer.mozilla.org/en-US/docs/Web/API/File/File) and providing it as the input to my component. |
| 16 | + |
| 17 | +This test case doesn't involve any interaction with the component so I jump straight to the assertion step. |
| 18 | + |
| 19 | +Here, two things have my interest: |
| 20 | + |
| 21 | +1. The component must render the name of the previewed file (i.e. `file.txt`); |
| 22 | +1. The component must render the content of the previewed file. |
| 23 | + |
| 24 | +I reflect those two expectations like so: |
| 25 | + |
| 26 | +```tsx |
| 27 | +expect(screen.getByText('file.txt')).toBeTruthy() |
| 28 | +expect(screen.getByText('hello world')).toBeTruthy() |
| 29 | +``` |
| 30 | + |
| 31 | +Looks like the first test is done. All that's left is to run via `npm test` and... Oh, no. |
| 32 | + |
| 33 | +```sh |
| 34 | + FAIL src/file-preview.test.tsx > displays the preview card |
| 35 | +TypeError: file.text is not a function |
| 36 | + ❯ src/file-preview.tsx:7:8 |
| 37 | + 5| |
| 38 | + 6| useEffect(() => { |
| 39 | + 7| file.text().then(setPreviewText) |
| 40 | + | ^ |
| 41 | + 8| }, [file]) |
| 42 | +``` |
| 43 | +
|
| 44 | +😱 Something must be terribly wrong here... |
| 45 | +
|
| 46 | +Except it's not. The `<FilePreview />` component calls the [`Blob.prototype.text`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/text) (`File` inherits from `Blob`) method to retrieve the file's content correctly. The test is also exceptionally short to have any hidden pitfalls. **All except for one**. |
| 47 | +
|
| 48 | +Turns out, the `Blob.prototype.text` method isn't implemented in JSDOM. You can reproduce this in isolation as well by running this code: |
| 49 | +
|
| 50 | +```ts |
| 51 | +import { JSDOM } from 'jsdom' |
| 52 | +
|
| 53 | +const dom = new JSDOM() |
| 54 | +const blob = new dom.window.Blob(['hello world']) |
| 55 | +await blob.text() |
| 56 | +// TypeError: (intermediate value).text is not a function |
| 57 | +``` |
| 58 | +
|
| 59 | +This is just one of the examples how introducing a custom, browser-like environment hurts your trust to your automated tests and the value you get from them. I really cannot stress this enough: _a valid test for a valid JavaScript code results in an error_. This test will run in Node.js as it will run in the browser, but not in JSDOM. |
0 commit comments