Jest and RTL: Traget Container is not a DOM element - reactjs

I am trying to test my component with jest and react testing library but jest seems to think ReactDOM.render is not a DOM element. Running the test gives this error
Below is my code and the things I tried to do:
index.tsx file:
import React from 'react';
import ReactDOM from 'react-dom';
import { applyMiddleware, compose, createStore } from 'redux';
import {Provider} from 'react-redux';
import './index.css';
import thunk from 'redux-thunk';
import combinedReducer from './main/reducers/combinedReducer';
import { MyProvider } from './CustomProviders';
import reportWebVitals from './reportWebVitals';
import { App } from './main/components/App';
const devTool = process.env.NODE_ENV === 'development' && (window as any).__REDUX_DEVTOOLS_EXTENSION__ ? (window as any).__REDUX_DEVTOOLS_EXTENSION__() : (f) => f;
export const store = createStore(combinedReducer, compose(applyMiddleware(thunk), devTool));
if (process.env.NODE_ENV === 'development') {
require('css-framework.css');
}
ReactDOM.render(
<React.StrictMode>
<MyProvider>
<Provider store={store}>
<App/>
</Provider>
</MyProvider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
App.tsx file:
import { useDispatch } from 'react-redux';
import {getFeatures, setContextInStoreActn, setUser, setProduct} from '../actions/GeneralActions';
import { useContext, useEffect } from 'react';
import { MyContext } from '../MyContext';
import { BasePage } from './BasePage';
export const App = () => {
const dispatch = useDispatch();
const context = useContext(MyContext);
useEffect(() => {
dispatch(setContextInStoreActn(context));
void context.getSelectedProduct().then((selectedProduct) => dispatch(setProduct(selectedProduct)));
void context.getImpersonatingUser().then((impersonatingUser) => dispatch(setUser(impersonatingUser)));
dispatch(getFeatures());
}, []);
return (
<div>
<BasePage />
</div>
);
};
App.test.tsx file:
import { Provider } from 'react-redux';
import { render } from '#testing-library/react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { App } from '../../main/components/App';
const middlewares = [thunk];
const general = {
general: ''
};
const store = configureMockStore(middlewares)({
general
});
const mockWait = () => new Promise((resolve) => setTimeout(resolve, 550));
test('renders without crashing', () => {
render(
<Provider store={store}>
<App/>
</Provider>
);
expect(render).toHaveBeenCalledWith(<Provider store={store}> <App/> </Provider>); //this never gets executed in the test
});
I looked at a couple solutions that suggest appending a div or root element to my test render but that did not seem to do anything.
Another thing I tried to do was upgrading everything to the latest versions (latest react, react-dom, jest, RTL, etc...) and test with that but there were too many dependency issues so I abandoned that route.
Edit:
After playing around with my App.tsx file I found out that the reason it fails is because of the useEffect, removing it makes the test pass but that is not ideal.

Related

matomo-tracker-react useMatomo returns "Invalid hook call"

im trying to implement matomo analytics within my react application (cra), however it trows an invalid hook call exception. Code has been copied from the simple example
https://www.npmjs.com/package/#datapunt/matomo-tracker-react
Did i miss something?
react: 17.0.2
react-dom: 17.0.2
code:
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";
import { MatomoProvider, createInstance } from "#datapunt/matomo-tracker-react";
const matomo = createInstance({
urlBase: "http://192.168.133.226/",
siteId: 3,
heartBeat: {
active: true,
seconds: 10,
},
linkTracking: false,
configurations: {
disableCookies: true,
setSecureCookie: true,
setRequestMethod: "POST",
},
});
ReactDOM.render(
<MatomoProvider value={matomo}>
<Provider store={store}>
<App />
</Provider>
</MatomoProvider>,
document.getElementById("root")
);
App.js
import React, { useEffect } from "react";
import "./App.scss";
import { BrowserRouter as Router, Link } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { useMatomo } from "#datapunt/matomo-tracker-react";
import Navigation from "./components/navigation/navigation";
import Sitemap from "./components/sitemap/sitemap";
import Main from "./pages/main/main";
function App() {
const modal = useSelector((state) => state.modal);
const items = useSelector((state) => state.items);
const app = useSelector((state) => state.app);
const dispatch = useDispatch();
const { trackPageView } = useMatomo();
useEffect(() => {
trackPageView();
// eslint-disable-next-line
}, []);
return (
<div className='app'>
<Router>
<Navigation />
<Sitemap />
<Main />
</Router>
</div>
);
}
export default App;
You need to create the Matamo instance first and then wrap everything around that.
Refer this https://www.npmjs.com/package/#datapunt/matomo-tracker-react
https://github.com/jonkoops/matomo-tracker/tree/main/packages/react
Use this one & try again:
npm install #jonkoops/matomo-tracker-react
import { MatomoProvider, createInstance } from '#jonkoops/matomo-tracker-react'
import { useMatomo } from '#jonkoops/matomo-tracker-react'

Field must be inside a component decorated with reduxForm(), Error in test file

When i run my tests, i get the error:
Field must be inside a component decorated with reduxForm()
I am mocking a store, so i would think that would take care of injecting redux on the test but, i'm not really sure.
Inside appointments.js I have a component that has a redux form
import React from 'react';
... other imports
import configureMockStore from 'redux-mock-store';
import { mount } from 'enzyme';
import expect from 'expect';
import { Provider } from 'react-redux';
import { IntlProvider } from 'react-intl';
import LoginSection from '../User/LoginSection';
import AppointmentsContainer from './AppointmentsContainer';
import Appointments from './Appointments';
import AppointmentStatus from .../Layout/AppointmentStatus/AppointmentStatusContainer';
jest.mock('./Appointments');
jest.mock('../User/LoginSection');
jest.mock('../Layout/AppointmentStatus/AppointmentStatusContainer');
const store = configureMockStore()({
form: 'Appointments',
});
const setup = (newProps) => {
const props = {
handleSubmit: jest.fn(),
},
form: 'appointmentsContainer',
locale: 'en',
...newProps,
};
const root = mount(
<Provider store={store}>
<IntlProvider {...props}>
<AppointmentsContainer {...props} />
</IntlProvider>
</Provider>
,
);
const wrapper = root.find(Appointments);
return {
root,
wrapper,
props,
};
};
describe('AppointmentsContainer', () => {
beforeEach(() => {
store.clearActions();
});
Any idea how can i fix this?

how to unit test react reduxsauce saga using jest and enzyme?

I am new to react and redux.
I am developing a project and for that I want to have redux, by using reduxsauce and redux-saga, but I am struggling to write unit tests for these.
Here is my folder structure:
My App-test.js:
import App from '../../../assets/src/App'
import React from 'react';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store'
import createStore from './Redux'
describe('App', () => {
const initialState = {output:100}
const mockStore = configureStore()
let store,container
const store = createStore()
beforeEach(()=>{
store = mockStore(initialState)
container = shallow(<App store={store} /> )
})
it('renders correctly', () => {
const rendered = renderer.create(
<App/>
);
expect(rendered.toJSON()).toMatchSnapshot();
});
});
Here is my App.js:
import React from 'react';
import ReactDOM from 'react-dom';
import Index from './Screens/Index';
import { Provider } from 'react-redux'
import createStore from './Redux'
const store = createStore()
const rootElement = document.getElementById('subscription');
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Index />
</Provider>
);
}
}
ReactDOM.render(<App />, rootElement);
I have tried with both the mockStore and store variable, but I am getting following error:
Any suggestions what could be wrong here?
Thanks
UPDATE 1
I muted the code now for shallow, and now my App-test.js file looks like this:
import App from '../../../assets/src/App'
import React from 'react';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store'
import createStore from './Redux'
describe('App', () => {
const initialState = {output:100}
const mockStore = configureStore()
let store,container
const store = createStore()
// beforeEach(()=>{
//// store = mockStore(initialState)
// container = shallow(<App store={store} /> )
// })
it('renders correctly', () => {
const rendered = renderer.create(
<App/>
);
expect(rendered.toJSON()).toMatchSnapshot();
});
});
But I get different error now:
UPDATE 2
After trying the solution as suggested by Rami Enbashi in the answer, the previous error (before UPDATE 1) again started appearing.
This seems to be a transpilation issue. You need to register Babel so that it will transpile ES6 to ES5 before you run unit tests. One way to do it is this.
In package.json add this jest config:
"jest": {
"setupTestFrameworkScriptFile": "./scripts/testsetup.js"
}
and in testsetup.js add this
require('babel-register')();
require("babel-polyfill");
require("babel-jest");
.....
Make sure you read Jest documentation for more config or needed plugins. And make sure you install them first.

Adding Routes to a React/Firebase app

Just starting to add React Routes to a React/firebase app. I had this code to read the data,
const fb = firebase
.initializeApp(config)
.database()
.ref();
fb.on('value', snapshot => {
const store = snapshot.val();
ReactDOM.render(
<App {...store} />
,
document.getElementById('root')
);
});
This worked correctly, with real time updates to the App.
I then started to play with Router,
ReactDOM.render(
<Router>
<Route path="/" component={App {...store}} />
</Router>
,
document.getElementById('root')
);
But the {...store} gives an error, unexpected token. Should I move the Firebase code lower down the tree into the App component or is there a different way?
uh the component=doesn't take that thing that you have there with ReactDOM.render()
As you have it right now: store will be undefined... so set:
let store = {} // at the top
Where is your actual component/class defined?
also you shouldn't creator render based on when Firebase shows up you should render first then when firebase shows up you can update the state.
So there is a lot that will need to be fixed here before this can work.
Here is my index.js:
also notice that store is the result of the configureStore (I'm assuming you want to use Redux as you have a store)...
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
import Root from './root/containers/Root'
import './index.css'
import configureStore from './root/store'
import { syncHistoryWithStore } from 'react-router-redux'
import { browserHistory } from 'react-router'
const store = configureStore()
const history = syncHistoryWithStore(browserHistory, store)
render(
<Root store={store} history={history} />,
document.getElementById('root')
);
My store:
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import reducer from '../reducers'
import { database, initializeApp } from 'firebase'
import { firebaseConfig } from '../constants'
initializeApp(firebaseConfig)
export const rootRef = database().ref().child('/react')
export const dateRef = rootRef.child('/date')
export default function configureStore(preloadedState){
const store = createStore(
reducer,
preloadedState,
applyMiddleware(thunkMiddleware, createLogger())
)
return store
}
And my Root:
import React, { Component, PropTypes } from 'react'
import { Provider } from 'react-redux'
import routes from './routes'
import { Router, } from 'react-router'
class Root extends Component {
render() {
const { store, history } = this.props
return (
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>
)
}
}
Root.propTypes = {
store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
}
Simplest Version to get started:
import React, { Component } from 'react'
import { firebaseConfig } from '../constants' //this is a must otherwise you have multiple versions of firebase running around...
initializeApp(firebaseConfig)
export const rootRef = database().ref().child('/root')
class App extends Component {
componentDidMount(){
initializeApp(firebaseConfig)
this.state = {store: {}}
rootRef.on('value', snapshot => {
this.setState({store: snapshot.val()});
}
}
render(){
let { store } = this.state
let childrenWithProps = React.Children
.map(this.props.children, function(child) {
return React.cloneElement(child, { store: store });
});
return <div>
{JSON.stringify(store)}
{childrenWithProps}
</div>
}
}
const Routes = (<Route component={App}><Route component={Comp1} path='/earg'/><Route component={Comp2} path='/earg'/></Route>)
render(Routes, document.getElementById('root'))
This is a lot of code and you'll need still more to get this going... I'd recommend a tutorial perhaps...
In the end, for a quick solution, I used an anonymous function to wrap the component,
<Route path="/" component={() => (<App {...store} />)} />

mocha with redux: expected undefined to equal true

I'm trying to wrote test for my react component which using redux and react-intl:
import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import Navbar from 'Navbar';
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux';
import messages from '../src/l10n/en.json'
import { IntlProvider } from 'react-intl'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
const store = mockStore({})
describe('<Navbar />', () => {
it('calls componentDidMount', () => {
const wrapper = mount(
<Provider store={store}>
<IntlProvider locale={ "en" } messages={ messages }>
<Navbar />
</IntlProvider>
</Provider>
);
expect(Navbar.prototype.componentDidMount.calledOnce).to.equal(true);
});
});
But I got this result:
<Navbar />
1) calls componentDidMount
0 passing (73ms)
1 failing
1) <Navbar /> calls componentDidMount:
AssertionError: expected undefined to equal true
Can some one give me an advise how can I fix it?
The error is because, componentDidMount is not spy'ed in the test. You could use sinon to do fix this issue. For instance,
import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import Navbar from 'Navbar';
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux';
import messages from '../src/l10n/en.json'
import { IntlProvider } from 'react-intl'
import { spy } from 'sinon';
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
const store = mockStore({})
describe('<Navbar />', () => {
it('calls componentDidMount', () => {
spy(Navbar.prototype, 'componentDidMount');
const wrapper = mount(
<Provider store={store}>
<IntlProvider locale={ "en" } messages={ messages }>
<Navbar />
</IntlProvider>
</Provider>
);
expect(Navbar.prototype.componentDidMount.calledOnce).to.equal(true);
});
});
On a side note:- If you want to use react-intl in tests, I would suggest to use helper functions as described here

Resources