I want to test the following React component
import React, { useContext, useState } from "react";
import { IntlProvider } from "react-intl";
export const Context = React.createContext();
const defaultLocale = navigator.language;
const LocalizationWrapper = (props) => {
const [locale, setLocale] = useState(defaultLocale);
function changeLocale(newLocale) {
setLocale(newLocale);
}
return (
<Context.Provider value={{ locale, changeLocale }}>
<IntlProvider locale={locale}>{props.children}</IntlProvider>
</Context.Provider>
);
};
For this, I have written test
import React from 'react';
import { render, unmountComponentAtNode } from "react-dom";
import { FormattedDate } from "react-intl";
import { act } from "react-dom/test-utils";
import LocalizationWrapper from './localizationWrapper';
let container = null;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("date formatted with default locale", () => {
act(() => {
render(
<LocalizationWrapper >
<FormattedDate value={new Date("2021-04-23")} />
</LocalizationWrapper>, container);
});
expect(container.textContent).toBe("4/23/2021");
});
How to add a test case that changes the locale that is part of LocalizationWrapper state, by using context or by changing state directly? From code outside of test I use context to change locale like so:
const context = useContext(Context);
context.changeLocale(locale);
You can render the FormattedDate component inside a test component so that you can get the context value. Then you can invoke the context.changeLocale function.
E.g.
index.jsx:
import React, { useState } from 'react';
import { IntlProvider } from 'react-intl';
export const Context = React.createContext();
const defaultLocale = navigator.language;
export const LocalizationWrapper = (props) => {
const [locale, setLocale] = useState(defaultLocale);
function changeLocale(newLocale) {
setLocale(newLocale);
}
return (
<Context.Provider value={{ locale, changeLocale }}>
<IntlProvider locale={locale}>{props.children}</IntlProvider>
</Context.Provider>
);
};
index.test.jsx:
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { act } from 'react-dom/test-utils';
import { FormattedDate } from 'react-intl';
import { LocalizationWrapper } from '.';
import { Context } from './';
describe('67228107', () => {
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it('date formatted with default locale', () => {
let renderedContext;
class TestComponent extends React.PureComponent {
static contextType = Context;
render() {
renderedContext = this.context;
return <FormattedDate value={new Date('2021-04-23')} />;
}
}
act(() => {
render(
<LocalizationWrapper>
<TestComponent />
</LocalizationWrapper>,
container
);
});
expect(container.textContent).toBe('4/23/2021');
act(() => {
renderedContext.changeLocale('fr');
});
expect(renderedContext.locale).toBe('fr');
});
});
Test result:
PASS stackoverflow/67228107/index.test.tsx (10.33 s)
67228107
✓ date formatted with default locale (36 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.jsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.615 s
package versions:
"react": "^16.14.0",
"react-dom": "^16.14.0",
"jest": "^26.6.3"
Related
I have a problem testing the component
import React from "react";
import { useSelector } from "react-redux";
import { PopupListWrapper, Song } from "./PopupListStyles";
const PopupList = () => {
const selectedAlbum = useSelector((state) => state.album);
return (
<PopupListWrapper data-testid="popupList-test">
{selectedAlbum.map((song) => {
const { trackName, trackTime } = song;
return (
<Song key={trackName}>
<p>{trackName}</p>
<p>{trackTime}</p>
</Song>
);
})}
</PopupListWrapper>
);
};
export default PopupList;
I want to check if it redner, but I still have a problem with its internal function
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";
import * as reactRedux from "react-redux";
import PopupList from "./PopupList";
describe("<PopupAlbumInformation/>", () => {
const initialState = {
selectedAlbum: [],
};
const mockStore = configureStore();
let store;
const useSelectorMock = jest.spyOn(reactRedux, "useSelector");
beforeEach(() => {
useSelectorMock.mockClear();
});
it("whether the component PopupList is rendering", () => {
store = mockStore(initialState);
useSelectorMock.mockReturnValue({ initialState });
const { selectedAlbum } = initialState;
const { getByTestId } = render(
<Provider store={store}>
<PopupList>{selectedAlbum}</PopupList>
</Provider>
);
});
});
And here is error
TypeError: selectedAlbum.map is not a function
10 | return (
11 | <PopupListWrapper data-testid="popupList-test">
> 12 | {selectedAlbum.map((song) => {
| ^
13 | const { trackName, trackTime } = song;
14 | return (
15 | <Song key={trackName}>
yes i know i don't have an assertion yet but because of this problem i can't go any further.
Any help?
There is no need to mock nor spy on the selector. Just wrap the component in redux provider, pass correct initial state and you are absolutely gucci
I know I need to pass the context to the component under test and I've tried a few different ways, but I can't seem to make it work. Under this setup, I'm getting this error:
TypeError: _react.default.useContext is not a function or its return value is not iterable
7 |
8 | function MyComponent(props) {
> 9 | const [locale, setLocale] = React.useContext(LocaleContext);
| ^
Any advice on what I'm doing wrong? Am I better off switching to enzyme or react-test-renderer?
App.js
import React from "react";
import { IntlProvider } from "react-intl";
import MyComponent from "./components/MyComponent ";
import "./App.css";
import { LocaleContext } from "./LocaleContext";
const messages = { zh: require("./translations/zh") };
function App() {
const [locale] = React.useContext(LocaleContext);
return (
<IntlProvider locale={locale} messages={messages[locale]}>
<div className="App">
<MyComponent />
</div>
</IntlProvider>
);
}
export default App;
LocaleContext.js
import React, { useContext } from "react";
export const LocaleContext = React.createContext();
export const useLocaleContext = () => useContext(LocaleContext);
export const LocaleContextProvider = props => {
const [locale, setLocale] = React.useState("en");
return (
<LocaleContext.Provider value={[locale, setLocale]}>
{props.children}
</LocaleContext.Provider>
);
};
MyComponent.js
import React from "react";
import { FormattedMessage } from "react-intl";
import { LocaleContext } from "../LocaleContext";
import logo from "../logo.svg";
function MyComponent(props) {
const [locale, setLocale] = React.useContext(LocaleContext);
const nextLocale = locale === "en" ? "zh" : "en";
return (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1>
<FormattedMessage id="title" defaultMessage="Hello World!" />
</h1>
<h2>
<FormattedMessage id="subtitle" defaultMessage="Welcome to our app" />
</h2>
<button onClick={() => setLocale(nextLocale)}>
Change language to {nextLocale}
</button>
</header>
);
}
export default MyComponent;
MyComponent.test.js
import React from "react";
import { render } from "#testing-library/react";
import * as LocaleContext from "../LocaleContext";
import MyComponentfrom "./MyComponent";
test("renders `hello world` heading", () => {
const contextValues = { title: "Hey There" };
jest
.spyOn(LocaleContext, "useLocaleContext")
.mockImplementation(() => contextValues);
const { getByText } = render(<MyComponent/>);
const helloWorldText = getByText(/hello world/i);
expect(helloWorldText).toBeInTheDocument();
});
package.json
{
"scripts": {
...
"test": "react-scripts test"
},
"dependencies": {
"#testing-library/jest-dom": "^4.2.4",
"#testing-library/react": "^9.3.2",
"react": "^16.13.1",
"react-intl": "^4.6.9",
"react-scripts": "3.4.1"
...
}
...
}
drew-reese gets credit for the answer, but I wanted to follow up the code I used, since it didn't end up using LocaleContext in the wrapper.
This also involved installing an extra dependency to compile with ICU.
npm i --save-dev full-icu
LocaleContext.js
import React, { useContext } from "react";
import { IntlProvider } from "react-intl";
const messages = { zh: require("./translations/zh") };
export const LocaleContext = React.createContext();
export const useLocaleContext = () => useContext(LocaleContext);
export const LocaleContextProvider = props => {
const [locale, setLocale] = React.useState("en");
return (
<LocaleContext.Provider value={[locale, setLocale]}>
{props.children}
</LocaleContext.Provider>
);
};
export const intlEnWrapper = {
wrapper: ({ children }) => <IntlProvider locale="en" messages={messages.en}>{children}</IntlProvider>
};
export const intlZhWrapper = {
wrapper: ({ children }) => <IntlProvider locale="zh" messages={messages.zh}>{children}</IntlProvider>
};
MyComponent.test.js
import React from "react";
import { render } from "#testing-library/react";
import { intlEnWrapper, intlZhWrapper } from "../../LocaleContext";
import MyComponentfrom "./index";
describe("For en locale", () => {
test("renders `Title Text` heading", () => {
const { getByText } = render(<MyComponent/>, intlEnWrapper);
const titleText = getByText(/title text/i);
expect(titleText).toBeInTheDocument();
});
});
describe("For zh locale", () => {
test("renders `Title Text` heading", () => {
const { getByText } = render(<MyComponent />, intlZhWrapper);
const titleText = getByText(/標題文字/i);
expect(titleText).toBeInTheDocument();
});
});
package.json
{
"scripts": {
...
"test": "cross-env NODE_ICU_DATA=node_modules/full-icu react-scripts test"
}
}
You may want to study the wrapper api, and setup for a custom render, but the gist is you create a test wrapper that provides the context provider for testing.
For example, I use react-intl so for testing I have a test utility intlWrapper
import React from 'react';
import { IntlProvider } from 'react-intl';
export const intlWrapper = ({ children }) => (
<IntlProvider locale="en">{children}</IntlProvider>
);
And to test a component it is used as such
const {/* query selectors */} = render(
<ComponentUsingIntl />,
{ wrapper: intlWrapper },
);
To suit your needs I think you should create a wrapper for your LocaleContextProvider
import { LocaleContextProvider } from '../LocaleContext';
export const contextWrapper = ({ children }) => (
<LocaleContextProvider>{children}</LocaleContextProvider>
);
You can now import your test contextWrapper and use
const { getByText } = render(<MyComponent/>, { wrapper: contextWrapper });
I cannot figure out how to jest mock firebase authentication with google login properly.I have the following code:
simple.tsx
import React, { Component } from 'react';
import * as firebase from 'firebase'
import { withRouter} from 'react-router-dom';
class simple extends Component {
signInWithgoogle() {
var provider = new firebase.auth.GoogleAuthProvider();
// how to mock provider when simulate click event.
firebaseApp.auth().signInWithRedirect(provider)
}
render() {
return (
<div>
<button onClick={this.signInWithgoogle.bind(this)}>
Login with Google
</button>
</div>
);
}
export default withRouter(connect(
)(simple));
simple.test.tsx
const mockStore = configureMockStore([thunk]);
const store = mockStore();
describe('<simple />', () => {
test("simulate click button", () =>{
const withProvider = (
<Provider store={store}>
<simple.WrappedComponent {...FirebaseAuthProps} />
</Provider>
);
const wrapper = mount(withProvider);
wrapper.find('Button').simulate('click');
});
});
Any help would be appreciated with examples?
Typescript namespace is some kind of a JS function after compiling and type erasure. After knowing this, of course, you can assign properties to JS function. Here is the solution:
simple.tsx:
import React, { Component } from 'react';
import firebase from 'firebase';
import { withRouter } from 'react-router-dom';
class simple extends Component {
signInWithgoogle() {
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithRedirect(provider);
}
render() {
return (
<div>
<button onClick={this.signInWithgoogle.bind(this)}>Login with Google</button>
</div>
);
}
}
export default withRouter(simple as any);
simple.spec.tsx:
import React from 'react';
import { mount } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import simple from './simple';
import firebase from 'firebase';
const mockStore = configureMockStore([thunk]);
const store = mockStore();
const FirebaseAuthProps = {};
jest.mock('firebase', () => {
const auth = jest.fn();
const mAuth = { signInWithRedirect: jest.fn() };
// #ts-ignore
auth.GoogleAuthProvider = jest.fn();
// #ts-ignore
auth.Auth = jest.fn(() => mAuth);
return { auth };
});
describe('<simple />', () => {
afterEach(() => {
jest.resetAllMocks();
});
test('simulate click button', () => {
// #ts-ignore
firebase.auth.mockImplementation(() => new firebase.auth.Auth());
const withProvider = (
<Provider store={store}>
<simple.WrappedComponent {...FirebaseAuthProps} />
</Provider>
);
const wrapper = mount(withProvider);
expect(wrapper.find('button').text()).toBe('Login with Google');
wrapper.find('button').simulate('click');
expect(firebase.auth.GoogleAuthProvider).toBeCalledTimes(1);
expect(firebase.auth).toBeCalledTimes(1);
expect(firebase.auth().signInWithRedirect).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58554920/simple.spec.tsx (15.341s)
<simple />
✓ simulate click button (82ms)
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
simple.tsx | 100 | 100 | 100 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 17.245s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58554920
I also have a similar solution, if you decide to use Firebase Auth with Firebase Analytics:
jest.mock('firebase/app', () => {
const analytics = jest.fn().mockReturnValue({
logEvent: jest.fn(),
});
const auth: any = jest.fn().mockReturnValue({
signInWithRedirect: jest.fn(),
getRedirectResult: jest.fn().mockResolvedValue({
credential: {
providerId: 'Google',
},
user: {
getIdToken: jest.fn().mockResolvedValue('abc1234'),
},
additionalUserInfo: {
profile: {
email: 'test#test.com',
name: 'John Doe',
},
},
}),
});
auth.GoogleAuthProvider = class {
addScope = jest.fn();
};
return { auth, analytics };
});
When using custom hooks with jest and enzymne I get this error 'TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined'
Tried various things but nothing seems to work. Please help
import React, { useEffect, useState, Fragment } from 'react';
/* Utils */
import { useSystemContext } from '../../../utils/Context/SystemContextProvider';
/* Components */
import MenuStructure from './MenuStructure';
const Menu = () => {
const [{ siteData }] = useSystemContext();
const [open, setOpen] = React.useState(false);
const openMenu = () => {
setOpen(!open);
};
const componentProps = {
menuItems: siteData,
openMenu: openMenu,
open: open
};
return (
<MenuStructure {...componentProps} />
)
};
export default Menu;
Test
import { shallow, mount } from 'enzyme';
import Menu from '.';
import { testUseState } from '../../../utils/TestUtils';
describe('<Menu />', () => {
let wrapper;
it('calls setCount with 0', () => {
wrapper = mount(<Menu />);
});
});
Error
TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
9 | import MenuStructure from './MenuStructure';
10 |
> 11 | const Menu = () => {
| ^
12 | const [{ siteData }] = useSystemContext();
13
added useSystemContext as requested
import React, { createContext, useContext, useReducer } from 'react';
export const SystemContext = createContext();
export const SystemContextProvider = ({ reducer, initialState, children }) => (
<SystemContext.Provider value={useReducer(reducer, initialState)}>
{children}
</SystemContext.Provider>
);
export const useSystemContext = () => useContext(SystemContext);
This is works fine in the site when running
I am using jest and enzyme for the unit testing in the react js.
When I console log the wrapper returned by shallow function by using "console.log(wrapper.debug())" it returns [function] and I am not able to find attribute in the component.
This is my signUp component
signUp.js
import React, { Component } from 'react'
import { withStyles, Card, CardContent, Typography, MenuItem } from '#material-ui/core';
import { darken } from '#material-ui/core/styles/colorManipulator';
import { FuseAnimate } from '#fuse';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux';
import { Link, withRouter } from 'react-router-dom';
import classNames from 'classnames';
import * as Actions from '../../../auth/store/actions/register.actions';
import * as FuseActions from '../../../store/actions/fuse/message.actions';
import Formsy from 'formsy-react';
import { TextFieldFormsy } from '#fuse';
import { Button, InputAdornment, Icon, Grid } from '#material-ui/core';
const styles = theme => ({
root: {
background: 'linear-gradient(to right, ' + theme.palette.primary.dark + ' 0%, ' + darken(theme.palette.primary.dark, 0.5) + ' 100%)',
color: theme.palette.primary.contrastText
}
});
class SignUp extends Component {
render() {
const { classes } = this.props;
const { canSubmit } = this.state;
return (
<div data-test="data-component" className={classNames(classes.root, "flex flex-col flex-1 flex-no-shrink p-24 md:flex-row md:p-0")}>
.........
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
userSignup: Actions.userSignup,
disableErrors: Actions.disableErrors,
showMessage: FuseActions.showMessage
}, dispatch);
}
function mapStateToProps({ auth }) {
return {
register: auth.register
}
}
export default withStyles(styles, { withTheme: true })(withRouter(connect(mapStateToProps, mapDispatchToProps)(SignUp)));
This is test file
signUp.test.js
import React from 'react';
import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import SignUp from './signUp';
import AppContext from '../../../AppContext';
configure({ adapter: new Adapter() });
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const setup = (initialState = {}) => {
const store = mockStore(initialState);
const wrapper = shallow(<SignUp store={store} />).dive().dive();
console.log(wrapper.debug());
return wrapper;
}
const findAttrByTest = (wrapper, val) => {
return wrapper.find(`[data-test="${val}"]`);
}
describe("render <SignUp>", () => {
let wrapper;
beforeEach(() => {
const initialState = {
error: '',
success: false
};
wrapper = setup(initialState);
});
test("render component without error", () => {
const component = findAttrByTest(wrapper, 'data-component');
expect(component.length).toBe(1);
});
});
Test result is
FAIL signUp.test.js (5.199s)
render <SignUp>
✕ render component without error (45ms)
● render <SignUp> › render component without error
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
43 | test("render component without error", () => {
44 | const component = findAttrByTest(wrapper, 'data-component');
> 45 | expect(component.length).toBe(1);
| ^
46 | });
47 | });
48 |
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 6.722s
Ran all test suites related to changed files.
Watch Usage: Press w to show more. console.log signUp.test.js:24
<ContextConsumer>
[function]
</ContextConsumer>
I have solved this issue by using.WrappedComponent.By this, you get access to the component
import React from 'react';
import { configure, shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import SignUp from './signUp';
configure({ adapter: new Adapter() });
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const push = jest.fn();
const historyMock = { push: jest.fn() };
const setup = (initialState = {}, props = {}) => {
const store = mockStore(initialState);
const wrapper = shallow(<SignUp.WrappedComponent history={historyMock} {...props} store={store} params={{
router:
push
}} />);
return wrapper;
}
const findAttrByTest = (wrapper, val) => {
return wrapper.find(`[data-test="${val}"]`);
}
describe("render <SignUp>", () => {
let wrapper;
beforeEach(() => {
const initialState = {
error: '',
success: false
};
wrapper = setup(initialState);
});
test("render component without error", () => {
const component = findAttrByTest(wrapper, 'data-component');
expect(component.length).toBe(1);
});
});