Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest with jsdom, document is undefined inside Promise resolve

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

  • Default create-react-app jest config with setupJest.js added to setupFiles
  • Run: yarn test

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 ?

like image 464
stilllife Avatar asked Feb 06 '18 12:02

stilllife


People also ask

What happens if a jest promise is rejected?

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'.

How to resolve undefined promise in promises?

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.

What is promise resolve in JS?

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.

What is the use of jsdom?

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!


1 Answers

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

Working tests

Now the tests work fine

like image 89
Tarun Lalwani Avatar answered Sep 18 '22 12:09

Tarun Lalwani