最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Jest with jsdom, document is undefined inside Promise resolve - Stack Overflow

programmeradmin1浏览0评论

The scenario

Trying to test a simple React ponent using Jest (and Enzyme). This ponent 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 ponent, 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 ponent', () => {
    it('Mounts', () => {
        const p = mount(<MyDropzone />)
        const dz = p.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 ?

The scenario

Trying to test a simple React ponent using Jest (and Enzyme). This ponent 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 ponent, 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 ponent', () => {
    it('Mounts', () => {
        const p = mount(<MyDropzone />)
        const dz = p.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 ?

Share asked Feb 6, 2018 at 12:07 stilllifestilllife 1,8061 gold badge20 silver badges39 bronze badges 8
  • stackoverflow./questions/41098009/mocking-document-in-jest – Daniel Conde Marin Commented Feb 6, 2018 at 12:14
  • 1 I'm experiencing the same issue reading file on disk with a promise using jest. – Guillaume Malartre Commented Mar 15, 2018 at 17:35
  • Everything is proper in jest setup. Using jsdom for document in test environment. Except for inside promise where document is null everywhere else document is defined. – Zword Commented Apr 24, 2018 at 14:41
  • @Zword, can you give a minimal repo for debugging? It would be much faster – Tarun Lalwani Commented Apr 24, 2018 at 19:07
  • @TarunLalwani here ya go. Interestingly enough, I found this was easy to reproduce with a JS app, but not with a TS app. – Mike Patrick Commented Apr 27, 2018 at 1:03
 |  Show 3 more ments

1 Answer 1

Reset to default 6 +50

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 bination

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

发布评论

评论列表(0)

  1. 暂无评论