11

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 ?

stilllife
  • 1,776
  • 1
  • 18
  • 38
  • https://stackoverflow.com/questions/41098009/mocking-document-in-jest – Daniel Conde Marin Feb 06 '18 at 12:14
  • 1
    I'm experiencing the same issue reading file on disk with a promise using jest. – Guillaume Malartre Mar 15 '18 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 Apr 24 '18 at 14:41
  • @Zword, can you give a minimal repo for debugging? It would be much faster – Tarun Lalwani Apr 24 '18 at 19:07
  • @TarunLalwani [here ya go](https://github.com/mikepatrick/jsdom-js-demo). Interestingly enough, I found this was easy to reproduce with a JS app, but not with a [TS app](https://github.com/mikepatrick/jsdom-ts-demo). – Mike Patrick Apr 27 '18 at 01:03
  • How are you running the tests? I did `npm test` and then `a` and all the tests passed for me https://i.stack.imgur.com/IBWVJ.png, `node -v -> v8.9.4` and `npm -v -> 5.7.1` – Tarun Lalwani Apr 27 '18 at 07:02
  • I'm just doing `yarn test`. I consistently observe this behavior when I clone a fresh copy of my sample repo: Using `yarn`, (for install and test) the tests pass the first two times I run them, and then consistently fail. Using `npm`, the tests pass the first time I run them, and then consistently fail. Do these tests _consistently_ pass across multiple runs for you? I'm using `node v8.9.4`, `npm v5.6.0`, and `yarn v1.3.2`. – Mike Patrick Apr 27 '18 at 10:46
  • Didn't get a notification for your last comment. Looking into this now – Tarun Lalwani Apr 30 '18 at 16:29

1 Answers1

6

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

Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • This may have helped for some thats why I am awarding the bounty but on importing jsdom-global I am getting this error `Failed to execute 'dispatchEvent' on 'EventTarget'` – Zword May 01 '18 at 12:19
  • Can you update the exception? I had a similar one when I forgot to remove `--env=jsdom` from `package.json` – Tarun Lalwani May 01 '18 at 12:24
  • I removed --env=jsdom and its giving me other errors like window.scrollTo is failing – Zword May 01 '18 at 12:26
  • I ran the test on the repo you had shared, that why I wanted the repo on the first place – Tarun Lalwani May 01 '18 at 12:28
  • Well this is my actual code: https://github.com/JesselJohn/react-components/. You can try on this one – Zword May 01 '18 at 12:43
  • The only failures I get are because of `React does not recognize the animationDuration prop on a DOM element`? – Tarun Lalwani May 01 '18 at 15:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/170156/discussion-between-zword-and-tarun-lalwani). – Zword May 01 '18 at 16:25
  • Sorry @TarunLalwani I didn't have time yet to test this out. I will accept the answer soon. There are a few things that bugs me though. Maybe you can elaborate: 1) what is the actual reason that causes document to be undefined2) I am not very happy to remove --env=jsdom and install js-dom global (as it doesn't sound the 'suggested' way of testing with enzyme 3) prepending window. also doesn't make me happy. I mean: if it works fine when promises are not involved, why should we we change all the non-test code (so source code) just to make jest behave? Thanks anyways for the debugging – stilllife May 02 '18 at 07:14