I am writing a test spec for the component that uses browserHistory. It throws an error
ReferenceError: navigator is not defined
i tried solution from Mocha-Chai throws "navigator not defined" due to React-router component but it is still not working. May be I am not being able to use the solution in right way.
Here is my spec file.
import React from 'react';
import { expect } from 'chai';
import jsdom from 'jsdom';
import sinon from 'sinon';
import shallow from 'enzyme';
import { MyComponent } from 'component.jsx';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.document = doc;
global.window = doc.defaultView;
global.navigator = {
userAgent: 'node.js',
};
describe('<Component />', () => {
let wrapper;
before(() => {
sinon.stub(Component.prototype, 'componentWillMount');
wrapper = shallow(<Component />);
});
context('Component', () => {
it('should render component', () => {
expect(wrapper.type()).to.equal('div');
});
});
after(() => {
Component.prototype.componentWillMount.restore();
});
});
Any help will be appreciated. Thank you.
Here is component.jsx file
import React, { Component, PropTypes } from 'react';
import R from 'ramda';
import { browserHistory } from 'react-router';
export class MyComponent extends Component {
componentWillMount() {
const query = this.props.location.query;
// looping through the query object of url
R.mapObjIndexed((value, key) => this.prepareStateData(value, key), query);
}
componentDidUpdate(prevProps) {
// this push the url every time the component is updated
if (this.props.urlHistory && this.props.urlHistory !== prevProps.urlHistory) {
browserHistory.push(this.props.urlHistory);
}
}
prepareStateData(value, key) {
// this changes the state according to the url
switch (key) {
case 'query1': {
// do something
break;
}
case 'query2': {
// do something
break;
}
default:
// do something
break;
}
}
render() {
return (
<div>
{ /* render part */ }
</div>
);
}
}
MyComponent.propTypes = {
location: PropTypes.object,
urlHistory: PropTypes.string,
};
Your test runner doesn't know anything about the environment that your app is supposed to run in, so window.navigator/window.location etc are not available.
browserHistory that you're using requires browser environment to work correctly and I assume that this is the issue you're facing. Try replacing imported browserHistory with createMemoryHistory and see if the test passes.
Excerpts from the docs which shows the difference:
browserHistory uses the HTML5 History API when available, and falls back to full refreshes otherwise. browserHistory requires additional configuration on the server side to serve up URLs, but is the generally preferred solution for modern web pages.
createMemoryHistory creates an in-memory history object that does not interact with the browser URL. This is useful for when you need to customize the history object used for server-side rendering, for automated testing, or for when you do not want to manipulate the browser URL, such as when your application is embedded in an .
Related
I'm using Redux to manage the state for my components. While it works on my application, I cannot get it to work within my react-testing-library testing suite.
Good news is, Redux-RTK has documentation for setting up their library with react-testing-library here (example shown below). Bad news is, I'm having difficulty converting it from jsx to tsx and doing whatever else needs to be done to get it to actually work within the tests.
redux docs
this is the wrapper they provide to give components (within tests) access to ... the redux store/behaviors?
// testing-utils.ts
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux'
// Import your own reducer
import alertReducer from '../alertSlice'
function render(
ui,
{
preloadedState,
store = configureStore({
reducer: { alert: alertReducer },
preloadedState,
}),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
when I write a test in react-testing-library, ie user clicks a button, the value I'm testing for (shown below) appears as "undefined".
import { render, screen } from "../../../testing-utils/test-utils";
test.only("when the user clicks the 'click me' button, a snackbar appears", async () => {
expect(screen.queryByText(/header/i)).toBeInTheDocument();
const clickMeButton = screen.getByRole("button", { name: /click me/i });
user.click(clickMeButton);
expect(
// screen.debug() shows: 'undefined header' not ... 'great job header'
await screen.findByText(/great job header/i)
).toBeInTheDocument();
});
component I'm testing against:
const SelectMedia = () => {
const alertState = useAppSelector(selectAlert);
const dispatch = useAppDispatch();
const createSnackBar = () => {
dispatch(
setAlert({
type: "success",
message: "great job",
display: "support-both",
})
);
};
return (
<SelectMediaWrapper>
{alertState && alertState.message + " header"}
// in tests, returns as "undefined header"
</SelectMediaWrapper>
)
}
I'm not sure where to take it from here, the testing-utils solution is based very close to the docs and my behaviors are very similar to what they used in the redux resource. However, I can't seem to get it to work and I'm not sure what I am missing in my implementation. Any thoughts?
I beleive the proper discription of this issue is explained here by timdorr. I tried exporting App.js from the bundle but get window undefined errors. SO still stuck
SSR/Client React Router Dom "Switch" breaks for me with a "Invariant Failed". I believe it says it has something to do with Switch not been allowed outside "Router", which it is inside.
The minimal project link is below, that may be easier way look at the project. I have listed the main files below
1: SERVER SIDE RENDER ENDPOINT
// EXPRESS ROUTER
const express = require("express");
const aRouter = express.Router();
// REACT UTILITIES
import React from "react";
import { renderToNodeStream } from "react-dom/server";
import { StaticRouter } from "react-router-dom";
import { createMemoryHistory } from "history";
import Loadable from "react-loadable";
// REDUX UTILITIES
import { init } from "../src/module/store";
import { Provider } from "react-redux";
// CUSTOM COMPONENTS
import App from "../src/App";
// UTILITIES
import fs from "fs-extra";
import renderUtils from "../utils/renderUtils";
// ASSETS
import { initState } from "../assets/store/init";
aRouter.get(["/", "/home"], async function (req, res) {
console.log("RENDER HOME");
try {
// INITIAL WRITE TO CLIENT - START HEAD
res.setHeader('Content-Type', 'text/html');
res.write("<!DOCTYPE html>");
res.write("<html style='scrollbar-width: none;'>");
let headHTML = await fs.readFile("./public/head.html", "utf-8");
let scriptsHTML = await fs.readFile("./public/scripts.html", "utf-8");
res.write(headHTML);
res.write("<body>");
res.write(`<div id = "app-container">`);
// INITALISING STATE
var initialState = initState();
initialState.article.articles = {
"abcde": {
title: "My First Article",
body: "This is my first article"
},
"fghij": {
title: "My Second Article",
body: "This is my second article"
},
"klmno": {
title: "My Third Article",
body: "This is my third article"
}
};
initialState.article.fetched = true;
initialState.ui.user = { type: "" };
initialState.ui.global = {
team: "Arsenal",
teamID: 19
};
const history = createMemoryHistory({ initialEntries: [req.originalUrl] });
const store = init(history, initialState);
// THE ISSUE SEEMS TO BE TO DO WITH THIS SERVER SIDE STATIC BROWSER AND THE CLIENT BORWSER ROUTER
const stream = renderToNodeStream(
<Provider store = {store}>
<StaticRouter history = {history} location = {req.originalUrl} context = {{}}>
<App />
</StaticRouter>
</Provider>
);
stream.pipe(res, { end: false });
stream.on("end", renderUtils.onRenderEnd.bind(this, res, store, scriptsHTML));
} catch (err) { renderUtils.onRenderError.bind(this, res, "RENDER HOME ERROR", err.message); }
});
var self = (module.exports = aRouter);
2: CLIENT INDEX
// REACT
import React from "react";
import ReactDOM from "react-dom";
import Loadable from "react-loadable";
import { BrowserRouter } from "react-router-dom";
import { createMemoryHistory } from "history";
import { Provider } from "react-redux";
// REDUX
import { init } from "./module/store";
// CREATE STORE
let history = createMemoryHistory();
let store = init(history, window.INITIAL_STATE);
// MAIN APP COMPONENT
import App from "./App";
// MOUNTED STYLES
import "./style/client/index.scss";
const renderApp = () => {
ReactDOM.hydrate(
<Provider store = {store}>
<BrowserRouter history = {history}>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("app-container")
);
};
store.subscribe(() => renderApp());
3: APP - CLIENT
// REACT
import React, { PureComponent } from "react";
import { Switch, Route } from "react-router-dom";
import PropTypes from "prop-types";
// REDUX STORE
import { connect } from "react-redux";
import { getName, getAge, getPosition } from "./module/user/userReducer";
import { getUIElement, setUIElement } from "./module/uiReducer";
// IMPORT CUSTOM COMPONENTS
import Routes from "./Routes";
class App extends PureComponent {
componentDidMount = () => this.props.setUI("user", "type", "admin");
render = () => {
return (
<div className = "app">
<span>My App</span>
<span>Name : {this.props.name}</span>
<span>Age : {this.props.age}</span>
<span>Position : {this.props.position}</span>
<span>Team : {this.props.team}</span>
<span>Team ID : {this.props.teamID}</span>
<span>Type : {this.props.type}</span>
<div>
<Switch>
<Route path = "/" component = {MyLocation} />
<Route path = "/contact" render = {() => (<MyLocation location = "Contact" />)} />
</Switch>
</div>
</div>
);
};
};
App.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
position: PropTypes.string.isRequired,
team: PropTypes.string.isRequired,
teamID: PropTypes.number.isRequired,
type: PropTypes.string.isRequired,
setUI: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
name: getName(state),
age: getAge(state),
position: getPosition(state),
team: getUIElement(state, "global", "team"),
teamID: getUIElement(state, "global", "teamID"),
type: getUIElement(state, "user", "type")
});
const mapDispatchToProps = dispatch => ({
setUI: (component, element, value) => dispatch(setUIElement({ component, element, value }))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
The full minimal react app here
It just breaks when I add the Switch and Routes. The Switch "IS INSIDE" the Browser Router. I have read articles which explain to send the same React Router Dom to the client, but I tried them explanations and they dont work for me.
To run the project simply run "yarn", "npm run build" and "npm start". The app has just one page with some filler text
Issue : Breaks at Switch
Required: Work at Switch
Tried: Explantions that explain to bring same instance of react-router-dom to client from server and use webpack alies etc.
Timdorr (Shared at start of question) explains this.
There is a new React.createContext API that we use in 5.0 to replace the legacy context usage. This involves creating a "context" pair, a set of Provider and Consumer components. We create that in a singleton module that is imported into Router and Link directly. In this new API, you have to use the exact same instance. Two separately-created contexts will never match up and won't be visible to each other.
Whats also funny is this works live on Heroku "production", but doesnt work locally "production". Im thinking heroku have some fallback code catching it.
Any help be great;
Daniel
After a lot of playing around I discovered the issue stemmed from
import { withRouter } from "react-router-dom";
export default withRouter(Component);
I think withRouter doesn't exists anymore on this new version and as as to why it worked with Heroku is a mystery. I think Heroku has great version controlling and debugging/handling.
I started using the hook useHistory instead and converted my classic components to function component with hooks and all is well now
Daniel
so i've been stuck for several days on an issue while implementing Unit Testing and Integration testing in a large production application that was built in Meteor/React tech stack. I am using the meteortesting:mocha package as recommended by the meteor documentation and enzyme.
The issue i am having is that i am not really grasping how i can mock the withTracker functionality. I am trying to use our dev database as the source for the test users and mock data. All of the props are generated in the tracker and then sent to the component it wraps. (Code sample below). Another issue i am having is that we are using meteor:universe for i18n internationalization. When mounting the component it shows plain text instead of the translated content. Wondering if there's a work around. Thanks in advance!
Component I am testing:
import React, { useState } from "react";
import ABCComponent from "./ABCComponent";
import XYZ from "./XYZComponent";
import * as ROUTE_CONSTANTS from "../../global/RoutesConstants";
import { withRouter } from "react-router-dom";
import { withTracker } from "meteor/react-meteor-data";
import UserAssessments from "../../collections/UserAssessments";
import moment from "moment-timezone";
import { i18n } from "meteor/universe:i18n";
const SortDashboard = (props) => {
const [isSkillsSort, setIsSkillSort] = useState(true);
return (
<div>
{/* Contains some logic to set 'isSetSkillSort' state true or false (business logic hidden for security purposes*/}
{isSkillsSort ? (
<ABCComponent user={props.user} skillsSorts={props.skillsSorts} employeeList={props.directReportEmp} />
) : (
<XYZComponent
user={props.user}
importanceSorts={props.importanceSorts}
employeeList={props.directReportEmp}
/>
)}
</div>
);
};
const SortDashboardTracker = withTracker((props) => {
if (!props.user) return {};
const abcSubscription = Meteor.subscribe("abcSubscription");
if (abcSubscription.ready()) {
const rawData = UserAssessments.find(
{ "assessor._id": Meteor.user().profile._id },
{ sort: { updatedDate: -1 } }
).fetch();
rawData.forEach((assessment) => {
//Do Something (business logic hidden for security purposes)
});
}
const xyzSubscription = Meteor.subscribe("xyzSubscription");
let directReportEmp = [];
if (xyzSubscription.ready()) {
directReportEmp = Meteor.users.find({ "profile.managerId": Meteor.user().username }).fetch();
}
return { importanceSorts, skillsSorts, directReportEmp };
})(SortDashboard);
export default withRouter(SortDashboardTracker);
My Test:
import {Meteor} from 'meteor/meteor';
import React from 'react';
import chai from 'chai';
import sinon, { mock } from 'sinon'
import {mount, shallow, configure, render} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {mockManager,mockEmp1,mockEmp2,mockEmp3,mockUser} from '../../mockUsers'
import SortDashboard from '../../../../imports/components/cllWizard/SortDashboard';
import { withRouter, BrowserRouter as Router } from "react-router-dom";
configure({adapter: new Adapter()});
if (Meteor.isClient) {
describe('WizardComponent', ()=> {
//let returnedText
//importing the mock user we created for testing purposes
const currentUser = mockUser
let props = {user: currentUser}
beforeEach(() => {
// now Meteor.user() will return the user we just imported
sinon.stub(Meteor, 'user');
Meteor.user.returns(currentUser);
// needed in methods
sinon.stub(Meteor, 'userId');
Meteor.userId.returns(currentUser._id);
});
//afterEach specifies that we want to restore the user after running the test
afterEach(() => {
Meteor.user.restore();
Meteor.userId.restore();
});
it('CLIENT: should render the Sort Dashboard', () => {
const wrapper = mount(<Router><SortDashboard.WrappedComponent {...props}/></Router>)
console.log(wrapper.debug())
});
});
}
TLDR;
Need to test a client side component that uses withTracker and withRouter
Need to be able to see the translated text from meteor:universe:i18n in the test
Pulling mock data from the db instead of manually creating it.
The issue may very well be my approach and lack of understanding. Please correct me where-ever necessary. Thanks in advance!
I am using jest and enzyme library for react testing with create-react-app boilerplate.
With the running of suite and test I am get in above error..
Could not found any solution yet.
Let me know if any solution.
TypeError: Cannot read property 'subroute' of undefined
Yes, I was rendering the connected component with passing the props into it.
So with that purpose all we need to pass store element into the Provider, and mount the component into it.
So all we need to understand is :
Mount: It will render the deep element of props and component associated with it.
Shallow: It will render the the first component of the top layer, not going the deep connected component as I was doing before with shallow.
Here are the code for and complete solution:
import { mountWrap } from '../contextWrap'
import { Provider } from 'react-redux'
import sinon from 'sinon'
import Login from '../components/Login/'
// import makeStore from '../redux/createStore'
import React from 'react'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const mockStore = configureMockStore([ thunk ])
const authDetails = {
'authDetails' : {
Terms :''
}
}
const match = {
params : {}
}
let actionSpy = sinon.spy()
let actionHistorySpy = sinon.stub({})
let authDetails_ = sinon.stub(authDetails)
let store
let component
/* eslint-disable */
describe('tests for MyContainerComponent', () => {
beforeEach(() => {
store = mockStore(authDetails)
component = mountWrap(<Provider store={ store }>
<Login history={actionHistorySpy} match={match} setGlobalLoaderStatus= {actionSpy} userDetail={authDetails_} />
</Provider>)
})
it('renders container', () => {
console.log(component.debug())
})
})
New to React, but not to test applications.
I'd like to make sure every time a component throws a error the ErrorBoundary message is displayed. If you don't know what I mean by ErrorBoundary here is a link.
I'm using Mocha + Chai + Enzyme.
Let's say we need to test React counter example using the following test configuration.
Test Configuration
// DOM
import jsdom from 'jsdom';
const {JSDOM} = jsdom;
const {document} = (new JSDOM('<!doctype html><html><body></body></html>')).window;
global.document = document;
global.window = document.defaultView;
global.navigator = global.window.navigator;
// Enzyme
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
// Chai
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
chai.use(chaiEnzyme());
UPDATE 1 - Some later thoughts
After reading this conversation about the best testing approach for connected components (which touches similar issues) I know I don't have to worry about componentDidCatch catching the error. React is tested enough and that ensures that whenever a error is thrown it will be caught.
Therefore there are only test two tests:
1: Make sure ErrorBoundary displays the message if there's any error
// error_boundary_test.js
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import ErrorBoundary from './some/path/error_boundary';
describe('Error Boundary', ()=>{
it('generates a error message when an error is caught', ()=>{
const component = shallow(<ErrorBoundary />);
component.setState({
error: 'error name',
errorInfo: 'error info'
});
expect(component).to.contain.text('Something went wrong.');
});
});
2: Make sure component is wrapped inside the ErrorBoundary (in the React counter example is <App />, which is misleading. The idea is to do that on the closest parent component).
Notes: 1) it needs to be done on the parent component, 2) I'm assuming children are simple components, not containers, as it might need more config.
Further thoughts: this test could be better written using parent instead of descendents...
// error_boundary_test.js
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import App from './some/path/app';
describe('App', ()=>{
it('wraps children in ErrorBoundary', ()=>{
const component = mount(<App />);
expect(component).to.have.descendants(ErrorBoundary);
});
To test ErrorBoundary component using React Testing Library
const Child = () => {
throw new Error()
}
describe('Error Boundary', () => {
it(`should render error boundary component when there is an error`, () => {
const { getByText } = renderProviders(
<ErrorBoundary>
<Child />
</ErrorBoundary>
)
const errorMessage = getByText('something went wrong')
expect(errorMessage).toBeDefined()
})
})
renderProviders
import { render } from '#testing-library/react'
const renderProviders = (ui: React.ReactElement) => render(ui, {})
This was my attempt without setting component state:
ErrorBoundary:
import React, { Component } from 'react';
import ErroredContentPresentation from './ErroredContentPresentation';
class ContentPresentationErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
render() {
return this.state.hasError ? <ErroredContentPresentation /> : this.props.children;
}
}
export const withErrorBoundary = WrappedComponent =>
props => <ContentPresentationErrorBoundary>
<WrappedComponent {...props}/>
</ContentPresentationErrorBoundary>;
And the test:
it('Renders ErroredContentPresentation Fallback if error ', ()=>{
const wrappedComponent = props => {
throw new Error('Errored!');
};
const component = withErrorBoundary( wrappedComponent )(props);
expect(mount(component).html()).toEqual(shallow(<ErroredContentPresentation/>).html());
});