In my Rails project, I am using the react-rails gem, which does the following:
window.React = React;
This is pretty handy, but when I run unit tests using Jest, that global is not there and I get an error from the file containing the component I am testing saying that React is not defined.
If I define React in the component file using
import React from 'react';
Then it causes errors due to loading React twice.
How should I define a global React variable in my unit tests so they work?
In your test file, do:
import React from 'react'
describe('something',() => {
window.React = React
// so when you require() your component, window.React is already set
var MyComponent = require('MyComponent').default
it('does something', () => {
// do something
})
})
Related
I've got a basic React Native app that has been barely modified and initialized using Expo:
expo init AwesomeProject
cd AwesomeProject
yarn start
The initialized app has this class:
import {
ColorSchemeName,
useColorScheme as _useColorScheme,
} from "react-native";
export default function useColorScheme(): NonNullable<ColorSchemeName> {
return _useColorScheme() as NonNullable<ColorSchemeName>;
}
I'm going through the codebase and trying to add unit tests. I've added a few rendering tests for React Native components, but here I'm trying to unit test the useColorScheme() function. Since this is a React hook, I can't call it outside of a React function body.
// Using jest
import useColorScheme from "../useColorScheme";
it('useColorScheme', () => {
const huh = useColorScheme();
});
So basically my question is - What's the best way to call a React hook using jest since the hooks can only be called from a React function? (Let it be noted that I am quite new to javascript, typescript, and React)
I want to create a Gatsby plugin with an index.js file but I want to render React components inside the plugin. I imported react:
const React = require('react');
const MyComponent = require('myComponent');
modules.exports = () => {
const myRenderedComponent = React.render(MyComponent);
...
}
But it fails because it doesn't understand the import statement I have in myComponent.js.
How can I render a component inside a Gatsby plugin instead of having to write the HTML?
I would be too silly to write the component's HTML by hand instead of just using the component itself
In the jest docs, I found this simple example of testing react components:
// Link.react.test.js
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';
test('Link changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseLeave();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
Why do we have to import React and react-test-renderer, but not have to import other test specific things, like test, expect?
Can someone explain, how this works under the hood and what actually happens when the tests are run?
It finds the binary jest and executes it with your script, this binary would compile your code first then run it, so those modules for testing would be imported during compiling time when those function keyword was found. You install Jest to your original application to test component. React module or others it really your stuff.
Update
By tracing the repository of Jest
jest/packages/jest-runtime/src/script_transformer.js, We could found out it utilize Node.js module VM to run the script, and it has some method like vm.createContext() and vm.Script().runInContext(), so those internal module should be imported to sandbox programmatically.
Example from VM
const vm = require('vm');
const sandbox = { globalVar: 1 }; // <=> import expect, test
vm.createContext(sandbox);
...
vm.runInContext('globalVar *= 2;', sandbox); // <=> Our test code.
So those module such as expect, and test may be imported like what vm.createContext() does above.
It's hard to exactly know how this be done in a short time, but we still could get some clues:
in jest/packages/jest-runtime/src/cli/index.js
...
import Runtime from '../'; // ---> jest/packages/jest-runtime/src/index.js
export function run(...) {
...
Runtime.createContext(
...
).then(
const runtime = new Runtime(config, environment, hasteMap.resolver);
runtime.requireModule(filePath);
...
)
}
Runtime is a critical class defined in
jest/packages/jest-runtime/src/index.js
...
import Resolver from 'jest-resolve';
...
import ScriptTransformer from './script_transformer';
...
requireModule() {
_execModule(...)
}
...
_execModule() {
...
this._createRequireImplementation(
...
this._createJestObjectFor(...)
}
Many critical works here, require module, detect the environment config, has Resolver to find the module id, to detect what kind of the module, should it be mocked, return jestObject, wrap all to our sandbox for testing.
Here is its core to do mock
I've got a nice little ES6 React component file (simplified for this explanation). It uses a library that is browser-specific, store This all works beautifully on the browser:
/app/components/HelloWorld.js:
import React, { Component } from 'react';
import store from 'store';
export default class HelloWorld extends Component {
componentDidMount() {
store.set('my-local-data', 'foo-bar-baz');
}
render() {
return (
<div className="hello-world">Hello World</div>
);
}
}
Now I'm trying to get it to render on the server as follows, using babel-register:
/server/routes/hello-world.js:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import HelloWorld from '../../app/components/HelloWorld'
export default function(req, res) {
res.render('root', {
reactHTML: ReactDOMServer.renderToString(<HelloWorld />),
});
}
I get an error from the node server saying "window is not defined" due to importing 'store'. Ideally I could conditionally import by detecting the environment (node vs browser), but conditional imports aren't supported in ES6.
What's best way to get around this? I don't actually need to execute the browser code (in this case componentDidMount won't be called by ReactDOMServer.renderToString) just get it running from node.
One way would be using babel-rewire-plugin. You can add it to babel-register via the plugins option
require('babel/register')({
plugins: ['babel-rewire-plugin']
});
Then rewire your store dependency to a mocked store:
HelloWorld.__Rewire__('store', {
set: () => {} // no-op
});
You can now render HelloWorld from the server peacefully.
If you want to suppress the load of some npm module, you can just mock it.
Put this on your node.js application setup, before the HelloWorld.js import:
require.cache[require.resolve('store')] = {
exports: {
set() {} // no-op
}
};
This value will be used instead of the real module, which doesn't need on your purposes. Node.js module API is stable, so this behavior will not be broken and you can rely on it.
I have a react application that doesn't uses the browserify tool.
It means that the React variable is exported by the script of the react js lib called in the <head>.
// React variable is already available
var MyComponent = React.createClass({});
After implementing this component, I want to create a test for it.
I took a look at Jest documentation and I've created my component test.
/** #jsx React.DOM */
jest.dontMock('../compiled_jsx/components/my-component.js');
describe('MyComponent', function() {
it('The variables are being passed to component', function() {
var React = require('react/addons');
// In the `MyComponent` import I got the error below:
// ReferenceError: /compiled_jsx/components/my-component.js: React is not defined
var myComponent = require('../compiled_jsx/components/my-component.js');
});
In the Jest documentation example, both component and its tests uses the require function for getting the React variable.
Is there any way to expose React variable into the component?
Or it's necessary using browserify for creating this test?
Jest runs in node.js, so you need to use commonjs modules. You don't use browserify with jest. If you're really against commonjs modules you can do this assuming each file is wrapped in an iffe.
var React = typeof require === 'undefined'
? window.React
: require('react/addons');
Or alternatively as the first line of your tests, try:
global.React = require('react/addons');
And either way, export your components using:
try { module.exports = Foo; } catch (e) { window.Foo = Foo };
Personally, I don't think jest is practical if you're not using commonjs modules.