I'm attempting to test a React component with Jest/Enzyme while using Webpack.
I have a very simple test #
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
it('App', () => {
const app = shallow(<App />);
expect(1).toEqual(1);
});
The relative component it's picking up is :
import React, { Component } from 'react';
import { render } from 'react-dom';
// import './styles/normalize.css';
class App extends Component {
render() {
return (
<div>app</div>
);
}
}
render(<App />, document.getElementById('app'));
However, running jest causes a failure:
Invariant Violation: _registerComponent(...): Target container is not a DOM element.
With errors #
at Object.<anonymous> (src/App.js:14:48)
at Object.<anonymous> (src/App.test.js:4:38)
The test files references line 4, which is the import of <App />, that causes a fail. The stack trace says line 14 of App.js is the reason for the failure -- which is nothing more than the render call from react-dom, something I've never had a challenge with (the app renders properly from my Webpack setup).
For those interested (Webpack code):
module.exports = {
entry: './src/App',
output: {
filename: 'bundle.js',
path: './dist'
},
module: {
loaders: [
{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
},
{
test: /\.css$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
},
{
test: /\.scss$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!sass'
}
]
}
}
And my package.json:
{
"name": "tic-tac-dux",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --devtool eval --progress --colors --inline --hot --content-base dist/",
"test": "jest"
},
"jest": {
"moduleNameMapper": {
"^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"^.+\\.(css|sass)$": "<rootDir>/__mocks__/styleMock.js"
}
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.17.0",
"babel-jest": "^16.0.0",
"babel-loader": "^6.2.5",
"babel-polyfill": "^6.16.0",
"babel-preset-es2015": "^6.16.0",
"babel-preset-react": "^6.16.0",
"css-loader": "^0.25.0",
"enzyme": "^2.4.1",
"jest": "^16.0.1",
"jest-cli": "^16.0.1",
"node-sass": "^3.10.1",
"react-addons-test-utils": "^15.3.2",
"react-dom": "^15.3.2",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
"webpack": "^1.13.2",
"webpack-dev-server": "^1.16.2"
},
"dependencies": {
"react": "^15.3.2",
"react-dom": "^15.3.2"
}
}
Oh, and if anyone is going to say that the div element isn't being loaded before the script, here's my index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>App</title>
</head>
<body>
<div id="app"></div>
<script src="/bundle.js"></script>
</body>
</html>
What could be the reason for this peculiar rendering problem? Something to do with a new Jest update to 15.0?
For anyone else that was combing through the internet like I've been - looking for a solution to this when testing with Jest - I will back up the answer by #biphobe by saying Jest will cause this error to occur when you export something inside the same file that is calling ReactDOM.render.
In my case, I was exporting an object within my index.js where I was also calling ReactDOM.render. I removed this export and voila!
App.jsx is supposed to export the App class and do nothing more, render should be called elsewhere.
If you remove the render call from the App.jsx error should disappear, it pops up because the test environment doesn't supply the DOM with an app id.
As I see, this error arises in many cases and requires different approaches to solve it. My scenario is not the same as the example above, I use redux & router, although I was struggling with the same error. What helped me to solve this problem is to change index.js from:
ReactDOM.render(
<Provider store={store}>
<AppRouter />
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
to:
ReactDOM.render(
(<Provider store={store}>
<AppRouter/>
</Provider>),
document.getElementById('root') || document.createElement('div') // for testing purposes
);
registerServiceWorker();
I found a solution for this error to my use case: Using the same Redux store React is using outside of React.
In trying to export my React's Redux store from index.tsx to be used somewhere else outside of the React application, I was getting the same error while running Jest tests (which make use of Enzyme) in the App.tsx file.
The error
The initial code that didn't work when testing React looked like this.
// index.tsx
import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import { applyMiddleware, compose, createStore } from "redux";
import App from "./components/App";
import { rootReducer } from "./store/reducers";
import { initialState } from "./store/state";
const middlewares = [];
export const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middlewares)),
);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root"),
);
The solution that worked for me
Separate the Redux store logic into a new file named store.ts, then create a default export (to be used by index.tsx, i.e., the React application) and a non-default export with export const store (to be used from non-React classes), as follows.
// store.ts
import { applyMiddleware, compose, createStore } from "redux";
import logger from "redux-logger";
import { rootReducer } from "./store/reducers";
import { initialState } from "./store/state";
const middlewares = [];
export const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middlewares)),
);
export default store;
// updated index.tsx
import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import App from "./components/App";
import store from "./store";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root"),
);
Using the Redux store in non-React classes
// MyClass.ts
import { store } from "./store"; // store.ts
export default class MyClass {
handleClick() {
store.dispatch({ ...new SomeAction() });
}
}
The default export
A small note before you go. Here is how to use the default and the non-default exports.
default export store; is used with import store from "./store";
export const store = ... is used with import { store } from "./store";
Hope this helps!
https://nono.ma/says/solved-invariant-violation-target-container-is-not-a-dom-element
Make sure in your test file you have well imported the render component.
It should be imported from #testing-library/react not from react-dom:
import { render } from '#testing-library/react';
Well we cant stop the developers from exporting component from any file and test it in isolation even if it have a react-dom import or usage in it .I mean what's wrong in it . We are not trying to disturb the whole file and test out some pieces of it as long as that is a valid piece of code .
Jest does not have an issue with react-dom , however conceptually they are diff . Jest is supposedly a browserless virtual test environment . React-DOM is a library which does the stitching of virtual DOM to real DOM for react components .
So obvious enough we can/should not test it in a normal way . But that is not the discussion for now. we are fine as long as our exported components are testable .
So Lets mock it
I did the mock in the testSetup file configured with "setupFilesAfterEnv" in jest config .
jest.mock("react-dom", () => {
return ({
"render": jest.fn()
})
})
That is pretty much worked for me. My react and react-dom code now happily go together in one file , works in browser and in the testing environment as well .
I have not encountered any issues because of this . If there is any I will be looking into the comment section
This solution worked for me. Just render if the element is there:
const root = document.getElementById('root');
if (root) {
render(
<App />,
root,
);
}
I found out this error can also be thrown when working with Portals in your tests. If you want to skip the error you can either mock Portals or add the Portal container element in your render method:
render (
<div>
<TestedComponent />
<div id="portal" />
</div>
)
Related
I'm new on React and I'm stuck on something. I added a new component as a 'react-customizable-progressbar' in my project. I have the following files in yellow. These were the ones that I created.
The problem point to
ERROR in ./src/example1.js 6:0-73
Module not found: Error: Can't resolve './CircularProgressBarContainer' in 'C:\Alex Maricoiu\Projects\React projects\project1\src'
Failed to compile.
CircularProgressBarContainer.tsx have the following code:
import React, {FunctionComponent} from "react";
import ICustomIndicatorProperties from './IProgressBarProperties';
import ProgressBar from 'react-customizable-progressbar'
const CustomProgressIndicator : FunctionComponent<ICustomIndicatorProperties> = ({value, title}) => {
return (
<div className="text-and-value" style={{width: "29em"}}>
<div className="title-text">{title}</div>
<div className="custom-progress-bar">
<ProgressBar
radius={100}
progress={value}
strokeWidth={18}
strokeColor="#5d9cec"
strokeLinecap="square"
trackStrokeWidth={18}
>
<div className="indicator">
<div>{value}%</div>
</div>
</ProgressBar>
</div>
</div>
)
};
export default CustomProgressIndicator;
Example1.js:
import {useState} from 'react';
import {CustomProgressIndicator} from './CircularProgressBarContainer';
function Button(props) {
return (
<button onClick={props.onClickFunc}>
Increment by 1
</button>
);
}
function Display(props){
// eslint-disable-next-line no-undef
const [counter, setCounter] = useState(0);
const incrementValue = () => setCounter(counter+1);
return (
<div>
<Button onClickFunc={incrementValue}/>
<b> Current value is: {counter}</b>
<CustomProgressIndicator
value={counter}
title ='Indicator Exercise 1'
/>
</div>
)
}
function App(props){
return (
<div>
<Display />
</div>
)
}
export default App;
The Interface file has (IProgressBarProperties.tsx):
interface ICustomIndicatorProperties {
title: string;
value: number;
}
export default ICustomIndicatorProperties;
And Main.js:
import './Main.css';
import Example1 from './example1';
import { Component } from 'react';
class App extends Component {
render(){
return (
<>
<div className='header'>Alex React Examples</div><div className='content'>
<h3>Example #1</h3>
<div id='example1Node'>
<Example1 />
</div>
</div>
</>
);
}
}
export default App;
Index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import MainApp from './Main';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<MainApp />
</React.StrictMode>
);
package.json:
{
"name": "project1",
"version": "0.1.0",
"private": true,
"dependencies": {
"#testing-library/jest-dom": "^5.16.5",
"#testing-library/react": "^13.4.0",
"#testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-customizable-progressbar": "^1.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
I use Node.js 19.3.0.
I tried to find from the browser console what is the problem, but I was stuck.
I tried to remove the node_modules and run this command again in terminal: npm install.
If I remove the reference to the file where is the progress bar the app is working, but with that failed with the error mentioned. Is there any way to find a solution to that error?
Thank you in advance
I use Node.js 19.3.0.
I tried to find from the browser console what is the problem, but I was stuck.
I tried to remove the node_modules and run this command again in terminal: npm install.
If I remove the reference to the file where is the progress bar the app is working, but with that failed with the error mentioned. Is there any way to find a solution to that error?
Try replacing import {CustomProgressIndicator} from './CircularProgressBarContainer'; with import CustomProgressIndicator from './CircularProgressBarContainer';, since you are using export default. Curly braces are used for normal (non-default) export. Alternatively remove default from export default.
UPDATE/RESOLVE
I came back with a solution. After I investigate a little seems that there was a incorrect path to the local module created (to the local TSX file).
The UPDATE was in Example1.js. When I put the path, intellisense show me the correct way to import and I hit Enter in order to complete the path. Fine for this. The import looked like this
import {CustomProgressIndicator} from './CircularProgressBarContainer';
The problem was just right here. I correct like it is bellow and it worked.
import {CustomProgressIndicator} from './CircularProgressBarContainer.tsx';
The final result is this. The component marked in yellow was the problem.
Thank you everyone.
Good afternoon all,
I am trying to import some React components from another application of mine, into my new Electron app. On the new Electron app, I changed aspects of index.html, so that I can reference ‘root’ in my App.js component(which I imported from my react-app to the electron app). I also imported my index.js from my react app into my new electron app, and nested it in the src file.
The Problem:
I am currently getting back a blank screen, and am not sure why. It might be that i referenced some files incorrectly, or a routing issue, but I’m not certain.
(Picture of Screen Attached Below)
File Directory:
The Code:
App.js:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import {
BrowserRouter as Router,
} from "react-router-dom";
import NavbarA from './src/components/NavbarA';
function App() {
return (
<div>
<Router>
<NavbarA/>
</Router>
</div>
);
}
export default App;
Main.js
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
Index.html
<!DOCTYPE html>
<html>
<div id="root"></div>
<title>Spotify</title>
<!-- You can also require other files to run in this process -->
<script src="./App.js>"></script>
</html>
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Package.json
{
"name": "electron-quick-start",
"version": "1.0.0",
"description": "A minimal Electron application",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"repository": "https://github.com/electron/electron-quick-start",
"keywords": [
"Electron",
"quick",
"start",
"tutorial",
"demo"
],
"author": "GitHub",
"license": "CC0-1.0",
"devDependencies": {
"electron": "^18.2.0"
},
"dependencies": {
"bootstrap": "^5.1.3",
"firebase": "^9.7.0",
"react": "^18.1.0",
"react-audio-player": "^0.17.0",
"react-bootstrap": "^2.3.1",
"react-is": "^18.1.0",
"react-router-dom": "^6.3.0",
"react-scripts": "^5.0.1"
}
}
Let Me Know What You Think!
Best,
-Zpo
Had a similar issue and including
import * as React from 'react';
in the component -- NavbarA in your case -- solved my issue.
I'm developing this application with Next.js i'm using typescript as a language. I'm also using module css to style my components. I initialized my next application by running the following command:
yarn create next-app .
Then few days back the application was running fine but not smoothly though. I'm using window 10 [Windows [Version 10.0.19042.1165] and my node version is v14.17.5 I'm also using yarn v1.22.10. I always face this problem when my next application grows large when I run:
yarn run dev
I get this:
yarn run v1.22.10
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Loaded env from ....
info - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
event - compiled successfully
event - build page: /
wait - compiling...
event - build page: /
but there's nothing that is displayed in the browser for more than 20 min the page will be loading and loading and loading. I'm thinking of changing to use gastby but i can't restart the whole process. If someone knows how to help me please help Here is my package.json file:
{
"name": "app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"#apollo/client": "^3.4.10",
"#material-ui/core": "^4.12.3",
"#material-ui/icons": "^4.11.2",
"#material-ui/lab": "^4.0.0-alpha.60",
"axios": "^0.21.1",
"firebase": "^9.0.0",
"graphql": "^15.5.2",
"next": "11.1.0",
"node-sass": "4.14.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-icons": "^4.2.0",
"react-redux": "^7.2.4",
"redux": "^4.1.1"
},
"devDependencies": {
"eslint": "7.32.0",
"eslint-config-next": "11.1.0",
"typescript": "^4.4.2"
}
}
Here is my index.tsx aka my home / code if it may make sense
import { NextPage } from "next";
import React from "react";
import styles from "../styles/Home.module.css";
import Header from "../components/minor/flesh/Header/Header";
import HeaderSkeleton from "../components/minor/skeletons/components/Header/HeaderSkeleton";
import Fleets from "../components/minor/flesh/Fleets/Fleets";
import FleetsSkeleton from "../components/minor/skeletons/components/Fleets/FleetsSkeleton";
import Form from "../components/minor/flesh/Form/Form";
import { IoIosCreate } from "react-icons/io";
import { IconButton } from "#material-ui/core";
import { ThemeType } from "../types/major";
import FormSkeleton from "../components/minor/skeletons/components/Form/FormSkeleton";
import { useQuery } from "#apollo/client";
import HELLO_WORLD_QUERY from "../graphql/queries/hello/hello";
import Post from "../components/minor/flesh/Post/Post";
import { useSelector } from "react-redux";
import PostSkeleton from "../components/minor/skeletons/components/Post/PostSkeleton";
import { apolloClient } from "../lib/apolloClient";
import USER_QUERY from "../graphql/queries/user/user";
import { useRouter } from "next/router";
interface Props {
data: any;
}
const Home: NextPage<Props> = ({ data }) => {
data = JSON.parse(data);
const router = useRouter();
const [showForm, setShowForm] = React.useState(false);
const theme = useSelector((state: any) => state.theme);
const className: string = `${styles.app} ${
theme === "dark" ? styles.dark__theme : styles.light__theme
}`;
return (
<div className={className}>
{/* <Header theme="light" /> */}
<HeaderSkeleton theme={theme} />
{/* <FormSkeleton theme={theme} /> */}
{showForm ? <Form theme={theme} setShowForm={setShowForm} /> : null}
<div className={styles.app__main}>
<Fleets theme={theme} />
<FleetsSkeleton theme={theme} />
<PostSkeleton theme={theme} />
<PostSkeleton theme={theme} />
<PostSkeleton theme={theme} />
<PostSkeleton theme={theme} />
<PostSkeleton theme={theme} />
<Post theme={theme} />
<Post theme={theme} />
<Post theme={theme} />
<Post theme={theme} />
</div>
<IconButton title="new post" onClick={() => setShowForm(true)}>
<IoIosCreate className={styles.home__create__post__icon} />
</IconButton>
</div>
);
};
Home.getInitialProps = async (context) => {
const user = await apolloClient.query({
query: USER_QUERY,
});
if (!user.data?.user) {
context.res.writeHead(307, { Location: "http://localhost:3000/welcome" });
return {
data: null,
};
}
return {
data: JSON.stringify(user, null, 2),
};
};
export default Home;
I am not sure, but it seems to work in my case. When I start server with different port (not the default 3000) it works just fine next dev -p 1234
Running it in an incognito window worked for me
Do a hard reload (Ctrl + Shift + R) if not running on port 3000.
I just had this same issue, localhost:3000 wasn't showing anything for about 15 minutes.
Please check if you made changes to your config file, "I hadn't". So I fixed it by going to next.cofig.js file and pressing Ctrl + Z, but changed nothing. This tricked Next.js to think there's a change in there.
Then I restarted the server and it displayed fine.
Just hit Ctrl + click on http://localhost:3000 from terminal.
You will get the below result to perform the above operation in the terminal after yearn dev or npm run dev.
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 357 ms (154 modules)
Try to rewrite your _app.js file. It can help to resolve your problem.
I'm using next.js with TypeScript and Material UI.
You can use the following code and replace that.
// pages/_app.tsx
import * as React from 'react';
import Head from 'next/head';
import { AppProps } from 'next/app';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { CacheProvider, EmotionCache } from '#emotion/react';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
export default function MyApp(props: MyAppProps) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
I want to remove jQuery gradually from my project in favor of React. I'm trying to use functional hooks to update the view after the Redux state changes. I coded up a working example on codepen.io. However, when I try to do this in my project, I get an invalid hook call error when trying to use a functional component. I seem to somehow have 2 versions of React, but I'm not sure why. Would this be caused by a webpack misconfiguration? Commenting out the call to useSelector in my comment supresses the error.
The console returns false for this check.
console.log(window.React1 === window.React2);
npm ls react
-- react#16.13.1
//package.json
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.1",
//.babelrc for babel 7
// Invalid hook call error persists if modules & targets are commented out and the defaults are used instead
"presets": [
[
"#babel/preset-env",
{
//turn off transpile to CommonJS
"modules": false,
//allow native async await
"targets": {
"node": "10"
}
}
],
"#babel/preset-react", //parse jsx syntax
]
}
//webpack.config.js
//rules
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
//MyUl.jsx
import React from 'react';
import { useSelector } from 'react-redux';
window.React2 = React;
console.log(window.React1 === window.React2);
let liKey = 0
let li = (count) => {
return <li key={liKey++}>{count}</li>;
};
let buildList = (count) => {
let list = [];
for (let i = count; i--;) {
list.push(li(count));
}
return list;
};
export const MyUl = () => {
const blockList = useSelector(state => state.blockList);
let count = 4;
return (
<ul id='list'>
<li>1st element</li>
{buildList(count)}
</ul>
);
};
//main.js
import React from 'react';
import { MyUl } from './MyUl';
ReactDOM.render(MyUl(), $('#blockLinks')[0]);
window.React1 = React;
It turns out I was missing the Provider component when I was rendering. "Invalid hook call" is a terrible description for this specific error.
//main.js
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
MyUl
<MyUl/>
</Provider>,
$('#blockLinks')[0]
);
I'm trying to figure out how to test an "onPress" event with Jest in a React-Native app so I can make sure the right function is called.
I went through the documentation and Google but couldn't find a solution for it in React-Native.
This is what I found that is supposed to work for React-Native with enzyme:
const mockFunc = jest.fn();
const component = mount(<MyComponent onPress={mockFunc} />);
component.simulate('press');
expect(mockFunc).toHaveBeenCalled();
But this doesn't work. Seems like mount doesn't work and I get this output:
ReferenceError: document is not defined
I tried with shallow instead but the TouchableOpacity is not getting rendered when I look at the output of the function... and you've guessed it, it doesn't work either. Not sure what to do.
Does anyone found a way to test events on React-Native?
Thanks
Enzyme does not support React-Native, because it's rendered differently and doesn't use the DOM. That's why you're getting the error ReferenceError: document is not defined. You can see this issue for more information. The React team is currently working to expose a .find() method in react-test-renderer to simulate actions on components. Then it should work for both React/React-native without needing a DOM environment.
There's a hack you can do (and that's what we did in our company) that is rendering a custom component that extends TouchableOpacity and map onClick to call onPress. Something like this:
const mockPressable = (name) => {
const RealComponent = require.requireActual(name);
class Component extends RealComponent {
render() {
return React.createElement(
RealComponent.displayName || RealComponent.name,
{ ...this.props, onClick: this.props.onPress },
this.props.children
);
}
}
return Component;
};
jest.mock('TouchableOpacity', () => mockPressable('TouchableOpacity'));
And in your test code, you call component.simulate('click').
It's a hack and I'm not sure what are the consequences of doing this but it has worked for our use cases.
You should use shallow instead, then called .dive()
const mockFunc = jest.fn();
const component = shallow(<MyComponent onPress={mockFunc} />);
component.dive().simulate('press');
expect(mockFunc).toHaveBeenCalled();
I'm able to run tests like what you've described in your question in React Native. Here is my configuration:
package.json
"scripts": {
...
"test": "node_modules/jest/bin/jest.js",
}
"devDependencies": {
...
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.1",
"enzyme-to-json": "^3.1.2",
"jest": "^21.2.1",
"jest-enzyme": "^4.0.0",
"jest-expo": "~21.0.0",
}
"jest": {
"preset": "jest-expo",
"setupFiles": [
"./test/jestSetup.js"
],
"snapshotSerializers": [
"./node_modules/enzyme-to-json/serializer"
]
}
test/jestSetup.js
import { configure, shallow, render, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
configure( { adapter: new Adapter() } )
// enzyme
global.shallow = shallow
global.render = render
global.mount = mount
Example component:
import React from 'react'
import { Button } from 'react-native'
const CancelButton = ( props ) =>
<Button
{ ...props }
onPress={ () => { props.navigation.goBack() } }
title="Cancel"
/>
export { CancelButton }
Example test
import React from 'react'
import { CancelButton } from '../CancelButton'
test( 'onPress', () => {
const goBackFunc = jest.fn()
const navigation = {
goBack: goBackFunc,
}
const component = shallow(
<CancelButton
navigation={ navigation }
/>
)
component.simulate( 'press' )
expect( goBackFunc ).toHaveBeenCalled()
} )
.babelrc
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
}
}