The scenario
Trying to test a simple React component using Jest (and Enzyme). This component uses react-dropzone
and I want to test some operations involving the DOM so I use jsdom (as already configured by create-react-app
)
The problem
The document
object while available in the my test code and also available inside of the component, is undefined
inside of the dropzone onDrop
callback, which prevents the test from running.
The code
MyDropzone
import React from 'react'
import Dropzone from 'react-dropzone'
const MyDropzone = () => {
const onDrop = ( files ) =>{
fileToBase64({file: files[0]})
.then(base64Url => {
return resizeBase64Img({base64Url})
})
.then( resizedURL => {
console.log(resizedURL.substr(0, 50))
})
}
return (
<div>
<Dropzone onDrop={onDrop}>
Some text
</Dropzone>
</div>
);
};
const fileToBase64 = ({file}) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
return resolve(reader.result)
}
reader.onerror = (error) => {
return reject(error)
}
reader.readAsDataURL(file)
})
}
/**
* Resize base64 image to width and height,
* keeping the original image proportions
* with the width winning over the height
*
*/
const resizeBase64Img = ({base64Url, width = 50}) => {
const canvas = document.createElement('canvas')
canvas.width = width
const context = canvas.getContext('2d')
const img = new Image()
return new Promise((resolve, reject) => {
img.onload = () => {
const imgH = img.height
const imgW = img.width
const ratio = imgW / imgH
canvas.height = width / ratio
context.scale(canvas.width / imgW, canvas.height / imgH)
context.drawImage(img, 0, 0)
resolve(canvas.toDataURL())
}
img.onerror = (error) => {
reject(error)
}
img.src = base64Url
})
}
export default MyDropzone;
MyDropzone.test.jsx
import React from 'react'
import { mount } from 'enzyme'
import Dropzone from 'react-dropzone'
import MyDropzone from '../MyDropzone'
describe('DropzoneInput component', () => {
it('Mounts', () => {
const comp = mount(<MyDropzone />)
const dz = comp.find(Dropzone)
const file = new File([''], 'testfile.jpg')
console.log(document)
dz.props().onDrop([file])
})
})
setupJest.js
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
configure({ adapter: new Adapter() })
Config
create-react-app
jest config with setupJest.js
added to setupFiles
Error
TypeError: Cannot read property 'createElement' of undefined
at resizeBase64Img (C:\dev\html\sandbox\src\MyDropzone.jsx:44:29)
at fileToBase64.then.base64Url (C:\dev\html\sandbox\src\MyDropzone.jsx:8:20)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
More info
Consider that document
is always defined if running that code in the browser, so to me the issue seems related with jsdom or Jest.
I am not sure if it is related with Promises, with the FileReaded or with the JS scope in general.
Maybe a bug on Jest side ?
Return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail. For example, let's say that fetchData, instead of using a callback, returns a promise that is supposed to resolve to the string 'peanut butter'.
Promise resolver undefined is not a function at new Promise (<anonymous>) The fix is straightforward: you must provide a way to resolve or reject promises: const promise = new Promise() const promise = new Promise(() => {}) That will fix the problem.
Promise.resolve () method in JS returns a Promise object that is resolved with a given value. Any of the three things can happend: If the value is a promise then promise is returned. If the value has a “then” attached to the promise, then the returned promise will follow that “then” to till the final state.
Jest ships with jsdom which simulates a DOM environment as if you were in the browser. This means that every DOM API that we call can be observed in the same way it would be observed in a browser!
So I was able to resolve this. The assumption that it works without any config changes is wrong. First you need to add few more packages added. Below is my updated package.json
{
"name": "js-cra",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-dropzone": "^4.2.9",
"react-scripts": "1.1.4",
"react-test-renderer": "^16.3.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"devDependencies": {
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"jest-enzyme": "^6.0.0",
"jsdom": "11.10.0",
"jsdom-global": "3.0.2"
}
}
Also I removed --env=jsdom
from the test script. As I was not able to make it work with that combination
After that you need to create a src/setupTests.js
, which is load globals for your tests. This where you need to load jsdom
and enzyme
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-enzyme';
import 'jsdom-global/register'; //at the top of file , even , before importing react
configure({ adapter: new Adapter() });
After that your tests would error out with below error
/Users/tarun.lalwani/Desktop/tarunlalwani.com/tarunlalwani/workshop/ub16/so/jsdom-js-demo/node_modules/react-scripts/scripts/test.js:20
throw err;
^
ReferenceError: FileReader is not defined
The issue seems to be that FileReader
should referred with a window
scope. So you need to update it like below
const reader = new window.FileReader()
And then run the tests again
Now the tests work fine
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With