6
votes

I have a minimum test react app with following component:

import React from 'react';
import $ from 'jquery';

export default class App extends React.Component {
    componentDidMount() {
        console.log('componentDidMount', $('#helloDiv').length);
    }

    render() {
        return <div id='helloDiv'>
            Hello React!
                </div>;
    }
}

this works fine when loading it in browser (Chrome). The console.log() in componentDidMount() prints out 1 helloDiv element found

However, if I run the test using mocha + enzyme + jsdom, the same console.log() in App component prints out 0:

import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import App from '../src/client/app/first'

describe('first test', () => {
    it('should pass', () => {        
        const app = mount(<App />);
        expect(app.find('#helloDiv').length).to.eq(1);
    });
});

Note: I don't have problem with this unit test, it's passing. The real problem is when < App /> is mounted using enzyme, componentDidMount() is called but the console.log() statement inside it prints out 0, instead of 1

Here is how I run mocha:

mocha --require enzyme/withDom --compilers js:babel-core/register test/index.test.js

Any idea why jquery selector doesn't find anything in the test? It should not be mocha issue because the same issue happens if I change to jest

3
Consider using cheerio for Node.Js applications instead of jQuery.Mijago
@Mijago thanks for the suggestion. However in my case I don't have much choice, at least for the moment. This is a simplified version of current project that I'm working on and jquery has been used throughout the projectPhuong Nguyen
Maybe app.find('#helloDiv') searches inside of app, but app itself has the id? Your jQuery version searches the whole document.Prinzhorn
@Prinzhorn sorry, I think my test is a bit misleading. I don't have problem with the expect itself. The problem is when mounting <App /> component, componentDidMount() is called, but the console.log() inside it prints out 0. I'll update the question to clarify thisPhuong Nguyen
I guess the answer still applies. The component is not actually mounted inside any document where jQuery could find it. It only exists inside the enzyme ReactWrapper?Prinzhorn

3 Answers

7
votes

Finally found the issue:

Enzyme mount(<SomeComponent />) by default will do full DOM rendering but not insert the rendered component into current document (JSDom). That's why jQuery cannot find any element in current document

To do full DOM rendering AND attach to current document:

mount(<SomeComponent />, { attachTo: document.getElementById('app') });

Where app is empty div available when jsdom is setup:

global.document = jsdom('<html><head></head><body><div id="app" /></body></html>');
0
votes

There needs to be some setup done before you could jsdom with jquery in node-env.

Try this if it helps.

Create a test helper file like this -

test_helper.js

import _$ from 'jquery';
import jsdom from 'jsdom';
import chai, { expect } from 'chai';
import chaiJquery from 'chai-jquery';

global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
global.navigator = global.window.navigator;
const $ = _$(window);

chaiJquery(chai, chai.util, $);

export {expect};

While running -

mocha --require enzyme/withDom --compilers js:babel-core/register --require test/test_helper.js test/index.test.js

or another way use jsdom-global without test_helper.js file.

npm install --save-dev jsdom-global

Then :

import 'jsdom-global/register'; 

//at the top of file , even  , before importing react
0
votes

I couldn't get Phuong Nguyen's answer to work. I did find the relevant page in the enzyme docs. I ended up with something like, based on the final example on that page:

const div = global.document.createElement('div');
global.document.body.appendChild(graphDiv);
const wrapper = mount(<SomeComponent />, { attachTo: div });  // same as the other answer
// clean up after ourselves
wrapper.detach();
global.document.body.removeChild(div);