jhnns / rewire

Easy monkey-patching for node.js unit tests
MIT License
3.08k stars 127 forks source link

Rewiring a module containing DOM manipulation #152

Open lkaratun opened 5 years ago

lkaratun commented 5 years ago

Hi,

I'm trying to use rewire from a Jest test. Jest comes with jsdom and documentobject is defined inside the test. However, when I try to run const app = rewire("../app.js") it fails with the error: TypeError: Cannot read property 'querySelector' of undefined when it tries to run the line const myForm = document.querySelector("#myForm");

When I replace the rewire call with require, the file get imported successfully.

Is there a way to solve this? Am I doing something wrong? Any help will be appreciated!

akuroda commented 5 years ago

Hi, It seems methods of document object are lost after rewire. My workaround for similar problem is:

const f = document.getElementById.bind(document);
const mymodule = rewire('...');
options.__set__("document", {
    getElementById: f
});
// access document object inside mymodule

cf: https://stackoverflow.com/questions/10743596/why-are-certain-function-calls-termed-illegal-invocations-in-javascript

atsepkov commented 5 years ago

The workaround will not work if the imported file performs DOM manipulation at the time it's being loaded rather than inside a function call to be executed later.

Granted, by default Jest's native jsdom fails in that case too, but explicitly loading jsdom via Jest's setupFiles flag easily fixes the issue in case of regular "require" but remains broken when using "rewire".

garrettwgg commented 4 years ago

It's not foolproof, but I came up with a different way to get around this. Basically I duplicated the file I wanted to test, injecting the jsdom directly into the file. I also used my actual HTML in the jsdom. I make no claims of it being a perfect (or perfectly written) solution, but it seems to work.

const fs = require('fs')
const rewire = require('rewire')
const pug = require('pug')

const rawIndex = fs.readFileSync('./public/javascripts/index.js', 'utf-8')

// this would a a readFileSync if you had a plain HTML file
const indexHTML = pug.compileFile('./views/index.pug')()

const mockDOM = `
'use strict';

const { JSDOM } = require('jsdom')
const dom = new JSDOM(\`${indexHTML}\`)
document = dom.window.document
window = dom.window
`

fs.writeFileSync('./__mocks__/index.jsdom.js', mockDOM + rawIndex, 'utf-8')

const index = rewire('../__mocks__/index.jsdom.js')

afterAll(() => {
    fs.unlinkSync('./__mocks__/index.jsdom.js')
})