I have below Jest config
jest: {
configure: {
testEnvironment: 'jsdom',
preset: 'ts-jest',
transform: {...},
moduleNameMapper: {
antd: '<rootDir>/__mocks__/antd/index.tsx'
},
testMatch: ['<rootDir>/src/**/*.test.(ts|tsx)$'],
},
},
In <rootDir>/__mocks__/antd/index.tsx
class Select extends Component<SelectProps> {
static Option: FC = (props) => <option {...props} data-testid="mock-selectOption" />;
render() {
const {
...props
} = this.props;
// #ts-ignore
return <select {...props} data-testid="mock-select" />;
}
}
I have a Select Component
import React from 'react';
import { Select as MySelect } from 'antd';
const { Option } = MySelect;
export interface SelectOption {
id: string;
label: string;
}
interface SelectProp {
options: SelectOption[];
selectedOption: string;
onChange: (key: string) => void;
}
function Select({ options, selectedOption, onChange }: SelectProp) {
return (
<MySelect value={selectedOption} onChange={onChange} >
{options.map((opt) => (
<Option key={opt.id} value={opt.id}>
{opt.label}
</Option>
))}
</MySelect>
);
}
export default Select;
I have a test case
import React from 'react';
import { fireEvent } from '#testing-library/react';
import { render } from 'setupTests';
import Select from './Select';
jest.mock('antd', () => {
const originalModule = jest.requireActual('antd');
return { ...originalModule };
});
describe('Select', () => {
const handleChange = jest.fn();
const mockProps = {
options: [],
onChange: handleChange,
};
it('render successfully', () => {
const { getByTestId } = render(<Select {...mockProps} />);
getByTestId('asca'); // use for debug
});
});
getByTestId('asca') will make the test case fails, then I see below DOM modal
TestingLibraryElementError: Unable to find an element by: [data-testid="asca"]
<body>
<div>
<select
data-testid="mock-select"
/>
</div>
</body>
which turns out still using the mock but not the actual antd component.
I've tried to add
beforeEach(() => {
jest.unmock('antd');
});
but still got the same result.
How can I use the actual module instead of mock?
Use jest.unmock(moduleName) API is correct,
Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module).
But you need to know:
When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block.
If you import the Select component and call jest.unmock() in beforeEach hook. When running the test case, the mock Select component has been imported, then the beforeEach hook execute. it's too late for jest to unmock the Select component.
Now, you have two options:
__mocks__/antd/index.tsx:
import { SelectProps } from "antd/lib/select";
import React, { Component, FC } from "react";
class Select extends Component<SelectProps<any>> {
static Option: FC = (props) => <option {...props} data-testid="mock-selectOption" />;
render() {
const { ...props } = this.props;
// #ts-ignore
return <select {...props} data-testid="mock-select" />;
}
}
Select.tsx:
import React from 'react';
import { Select as MySelect } from 'antd';
const { Option } = MySelect;
export interface SelectOption {
id: string;
label: string;
}
interface SelectProp {
options: SelectOption[];
selectedOption: string;
onChange: (key: string) => void;
}
function Select({ options, selectedOption, onChange }: SelectProp) {
return (
<MySelect data-testid='asca' value={selectedOption} onChange={onChange}>
{options.map((opt) => (
<Option key={opt.id} value={opt.id}>
{opt.label}
</Option>
))}
</MySelect>
);
}
export default Select;
1. Call jest.unmock() API in module scope of test file(Put it after the import statement is ok, it will be hoisted to the top of the code block)
import React from 'react';
import { render } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import Select from './Select';
jest.unmock('antd');
describe('Select', () => {
it('render successfully', async () => {
const handleChange = jest.fn();
const mockProps = {
options: [],
onChange: handleChange,
selectedOption: '',
};
const { getByTestId } = render(<Select {...mockProps} />);
expect(getByTestId('asca')).toBeInTheDocument();
});
});
2. Call jest.unmock() API in beforeEach hook or it functional scope, then dynamic import the Select component.(Dynamic import statement must put after the jest.unmock() API)
import React from 'react';
import { render } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
describe('Select', () => {
it('render successfully', async () => {
jest.unmock('antd');
const Select = (await import('./Select')).default
const handleChange = jest.fn();
const mockProps = {
options: [],
onChange: handleChange,
selectedOption: '',
};
const { getByTestId } = render(<Select {...mockProps} />);
expect(getByTestId('asca')).toBeInTheDocument();
});
});
Test result:
PASS stackoverflow/73274190/Select.test.tsx (11.55 s)
Select
✓ render successfully (1156 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.049 s, estimated 13 s
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['jest-extended'],
setupFiles: ['./jest.setup.js'],
};
package version:
"antd": "^4.16.12",
"jest": "^26.6.3",
Related
I am using next 13.1.0.
I have a ContextProvider that sets a light and dark theme
'use client';
import { Theme, ThemeContext } from '#store/theme';
import { ReactNode, useState, useEffect } from 'react';
interface ContextProviderProps {
children: ReactNode
}
const ContextProvider = ({ children }: ContextProviderProps) => {
const [theme, setTheme] = useState<Theme>('dark');
useEffect(() => {
const storedTheme = localStorage.getItem('theme');
if (storedTheme === 'light' || storedTheme === 'dark') {
setTheme(storedTheme);
} else {
localStorage.setItem('theme', theme);
}
// added to body because of overscroll-behavior
document.body.classList.add(theme);
return () => {
document.body.classList.remove(theme);
};
}, [theme]);
const toggle = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
};
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
};
export { ContextProvider };
I use it in my root layout
import '#styles/globals.scss';
import { GlobalContent } from '#components/GlobalContent/GlobalContent';
import { ContextProvider } from '#components/ContextProvider/ContextProvider';
import { Inter } from '#next/font/google';
import { ReactNode } from 'react';
const inter = Inter({ subsets: ['latin'] });
interface RootLayoutProps {
children: ReactNode
}
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html lang="en" className={inter.className}>
<head />
<body>
<ContextProvider>
<GlobalContent>
{children}
</GlobalContent>
</ContextProvider>
</body>
</html>
);
};
export default RootLayout;
And I consume the theme value in my GlobalContent
'use client';
import styles from '#components/GlobalContent/GlobalContent.module.scss';
import { GlobalHeader } from '#components/GlobalHeader/GlobalHeader';
import { GlobalFooter } from '#components/GlobalFooter/GlobalFooter';
import { ThemeContext } from '#store/theme';
import { ReactNode, useContext } from 'react';
interface GlobalContentProps {
children: ReactNode
}
const GlobalContent = ({ children }: GlobalContentProps) => {
const { theme } = useContext(ThemeContext);
return (
<div className={`${theme === 'light' ? styles.lightTheme : styles.darkTheme}`}>
<GlobalHeader />
<div className={styles.globalWrapper}>
<main className={styles.childrenWrapper}>
{children}
</main>
<GlobalFooter />
</div>
</div>
);
};
export { GlobalContent };
I get the error
Hydration failed because the initial UI does not match what was rendered on the server.
React docs error link
I don't understand why I am getting this error because I am accessing localStorage inside my useEffect, so I expect the HTML generated on the server to be the same with the client before the first render.
How can I solve this error?
I have made a workaround that solves the issue for now at the cost of giving up SSR.
By using a dynamic import on my ContextProvider, I disable server-rendering and the error is gone. As a bonus, the flashing issue from my default dark theme to my light theme saved on localStorage is gone. But I give up the benefits of SSR. If someone finds a better solution, please do share.
import '#styles/globals.scss';
import { GlobalContent } from '#components/GlobalContent/GlobalContent';
import { Inter } from '#next/font/google';
import dynamic from 'next/dynamic';
import { ReactNode } from 'react';
const inter = Inter({ subsets: ['latin'] });
interface RootLayoutProps {
children: ReactNode
}
// Fixes: Hydration failed because the initial UI does not match what was rendered on the server.
const DynamicContextProvider = dynamic(() => import('#components/ContextProvider/ContextProvider').then(mod => mod.ContextProvider), {
ssr: false
});
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html lang="en" className={inter.className}>
<head />
<body>
<DynamicContextProvider>
<GlobalContent>
{children}
</GlobalContent>
</DynamicContextProvider>
</body>
</html>
);
};
export default RootLayout;
This solution does not disable SSR site wide. I added a new test page with the following code
async function getData() {
const res = await fetch('https://rickandmortyapi.com/api/character', { cache: 'no-store' });
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return (
<main>
{data.results.map((c: any) => {
return (
<p key={c.id}>{c.name}</p>
);
})}
</main>
);
}
After running npm run build, I can see that the test page is using ssr
On checking the response for the test page, I can see the HTML response
I solved the error just by dynamically importing the default export of ContextProvider like this in _app.tsx . I'm also persisting context state in localStorage and it works without a problem .
_app.tsx
import dynamic from "next/dynamic";
const TodoProvider = dynamic(
() => import("#/util/context").then((ctx) => ctx.default),
{
ssr: false,
}
);
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<TodoProvider>
<Component {...pageProps} />
</TodoProvider>
);
}
context.tsx
import React, {
useState,
FC,
createContext,
ReactNode,
useEffect,
} from "react";
export const TodoContext = createContext<TodoContextType | null>(null);
interface TodoProvider {
children: ReactNode;
}
const getInitialState = () => {
if (typeof window !== "undefined") {
const todos = localStorage.getItem("todos");
if (todos) {
return JSON.parse(todos);
} else {
return [];
}
}
};
const TodoProvider: FC<TodoProvider> = ({ children }) => {
const [todos, setTodos] = useState<ITodo[] | []>(getInitialState);
const saveTodo = (todo: ITodo) => {
const newTodo: ITodo = {
id: Math.random(),
title: todo.title,
description: todo.description,
status: false,
};
setTodos([...todos, newTodo]);
};
const updateTodo = (id: number) => {
todos.filter((todo: ITodo) => {
if (todo.id === id) {
todo.status = !todo.status;
setTodos([...todos]);
}
});
};
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("todos", JSON.stringify(todos));
}
}, [todos]);
return (
<TodoContext.Provider value={{ todos, saveTodo, updateTodo }}>
{children}
</TodoContext.Provider>
);
};
export default TodoProvider;
iam having trouble with covering function inside react context
Before that, let me show you the snippet of the code
import {
useCallback,
useEffect,
useState,
createContext,
ReactNode,
FC
} from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import PropTypes from 'prop-types';
import { pushBackRoute } from '../libs/helpers/backRouteHelper';
export interface BottomBarProviderProps {
children: ReactNode;
}
export interface IBottomBarProvider {
selectedMenu: string;
handleClickMenu: (item: { name: string; route: string }) => void;
}
export const defaultValueBottomBarContext = {
selectedMenu: '',
handleClickMenu: () => {}
};
export const BottomBarContext = createContext<IBottomBarProvider>(
defaultValueBottomBarContext
);
export const BottomBarProvider: FC<BottomBarProviderProps> = ({
children,
...props
}) => {
const history = useHistory();
const location = useLocation();
const [selectedMenu, setSelectedMenu] = useState('Belanja');
const handleClickMenu = useCallback(
(item) => {
setSelectedMenu(item.name);
pushBackRoute(location.pathname);
history.push(item.route);
},
[selectedMenu, location?.pathname]
);
useEffect(() => {
Iif (location.pathname === '/marketplace/history') {
setSelectedMenu('Riwayat');
}
}, [location.pathname]);
const value: IBottomBarProvider = {
selectedMenu,
handleClickMenu
};
return (
<BottomBarContext.Provider
value={value}
{...props}
data-testid="bottom-bar-context"
>
{children}
</BottomBarContext.Provider>
);
};
BottomBarProvider.propTypes = {
children: PropTypes.node
};
BottomBarProvider.defaultProps = {
children: null
};
So i managed to cover most of the hooks and render, but the function/handle function inside this context is really hard
And this is my current test code, below :
/* eslint-disable jest/prefer-called-with */
import { render, screen } from '#testing-library/react';
import { BottomBarProvider } from './BottomBarContext';
import '#testing-library/jest-dom/extend-expect';
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => ({
pathname: '/marketplace'
}),
useHistory: () => ({
push: mockHistoryPush
})
}));
describe('BottomBarProvider', () => {
it('should render the children and update the selected menu when handleClickMenu is called', () => {
//Arrange
const children = <div data-testid="children">Hello, World!</div>;
//Act
render(<BottomBarProvider>{children}</BottomBarProvider>);
//Assert
expect(screen.getByTestId('children')).toHaveTextContent('Hello, World!');
});
});
Do you guys have any idea how to cover the function inside this context ?
Mock functions, hooks, and components of the module you don't own are not recommended. The useLocation and useHistory hooks in your case. Incorrect mock will break their functions which causes your tests to pass based on incorrect mock implementation.
Use <MemoryRouter/> component for testing history change. See official example of RTL about how to test react router
You should also create a test component to consume the React Context for testing the <BottomBarProvider/> component and BottomBarContext. The key is to trigger the handleClickMenu event handler by firing a click event on some element of the test component.
E.g.
BottomBarContext.tsx:
import React from 'react';
import {
useCallback,
useEffect,
useState,
createContext,
ReactNode,
FC
} from 'react';
import { useHistory, useLocation } from 'react-router-dom';
// import { pushBackRoute } from '../libs/helpers/backRouteHelper';
export interface BottomBarProviderProps {
children: ReactNode;
}
export interface IBottomBarProvider {
selectedMenu: string;
handleClickMenu: (item: { name: string; route: string }) => void;
}
export const defaultValueBottomBarContext = {
selectedMenu: '',
handleClickMenu: () => { }
};
export const BottomBarContext = createContext<IBottomBarProvider>(
defaultValueBottomBarContext
);
export const BottomBarProvider: FC<BottomBarProviderProps> = ({
children,
...props
}) => {
const history = useHistory();
const location = useLocation();
const [selectedMenu, setSelectedMenu] = useState('Belanja');
const handleClickMenu = useCallback(
(item) => {
setSelectedMenu(item.name);
// pushBackRoute(location.pathname);
history.push(item.route);
},
[selectedMenu, location?.pathname]
);
useEffect(() => {
if (location.pathname === '/marketplace/history') {
setSelectedMenu('Riwayat');
}
}, [location.pathname]);
const value: IBottomBarProvider = {
selectedMenu,
handleClickMenu
};
return (
<BottomBarContext.Provider value={value} {...props} data-testid="bottom-bar-context">
{children}
</BottomBarContext.Provider>
);
};
BottomBarContext.test.tsx:
import { fireEvent, render, screen } from '#testing-library/react';
import '#testing-library/jest-dom';
import React, { useContext } from 'react';
import { MemoryRouter, Route, Switch } from 'react-router-dom';
import { BottomBarContext, BottomBarProvider } from './BottomBarContext';
describe('BottomBarProvider', () => {
test('should pass', async () => {
const TestComp = () => {
const bottomBarContext = useContext(BottomBarContext);
const menuItems = [
{ name: 'a', route: '/a' },
{ name: 'b', route: '/b' },
];
return (
<>
<ul>
{menuItems.map((item) => (
<li key={item.route} onClick={() => bottomBarContext.handleClickMenu(item)}>
{item.name}
</li>
))}
</ul>
<p>selected menu: {bottomBarContext.selectedMenu}</p>
<Switch>
{menuItems.map((item) => (
<Route key={item.route} path={item.route} component={() => <div>{item.name} component</div>} />
))}
</Switch>
</>
);
};
render(
<MemoryRouter initialEntries={['/']}>
<BottomBarProvider>
<TestComp />
</BottomBarProvider>
</MemoryRouter>
);
const firstListItem = screen.getAllByRole('listitem')[0];
fireEvent.click(firstListItem);
expect(screen.getByText('selected menu: a')).toBeInTheDocument();
expect(screen.getByText('a component')).toBeInTheDocument();
});
});
Test result:
PASS stackoverflow/74931928/BottomBarContext.test.tsx (16.574 s)
BottomBarProvider
✓ should pass (116 ms)
----------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------------|---------|----------|---------|---------|-------------------
All files | 95 | 66.67 | 75 | 94.74 |
BottomBarContext.tsx | 95 | 66.67 | 75 | 94.74 | 50
----------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 18.184 s, estimated 23 s
Coverage HTML reporter:
I am new to writing test cases for React. Can someone tell me how to proceed with writing test cases for this file and how to finish code coverage.
How do i test mapDispatchToProps, componentDidMount or handleClick functions below. Can someone explain me how to proceed with steps to achieve test cases.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import DOMPurify from 'dompurify'
import escape from 'escape-html'
import Message from 'wf-dbd-react-ui/es/Message'
import ContentEventWrapper from 'wf-dbd-react-ui/es/ContentEventWrapper'
import { unescapeHtml } from 'wf-dbd-react-ui/es/lib'
import { requestNavigation } from 'wf-dbd-react-ui/es/actions'
import NavigationItemRecord from 'wf-dbd-react-ui/es/lib/records/NavigationItemRecord'
import ScrollToTopOnMount from 'wf-dbd-react-ui/es/ScrollToTopOnMount'
class MessageDisplayWithSpecialCharacters extends Component { //NOSONAR
constructor(props) {
super(props)
this.elementRef = null
}
componentDidMount() {
if (this.props.focusOnMount) {
if (this.elementRef) {
this.elementRef.blur() //needed to reset focus in iOS
this.elementRef.focus()
setTimeout(() => { this.elementRef.focus() }, 100) //timeout needed for Android
}
}
}
setElementRef = element => {
this.elementRef = element
}
handleClick = ({ target }) => {
const { requestNavigation } = this.props
if (target.hasAttribute('data-cui-link')) {
const navigationItem = new NavigationItemRecord({
samlNavigation: true,
displayType: 'saml',
navigationUrl: target.getAttribute('data-cui-link')
})
requestNavigation(navigationItem)
}
}
render() {
const { messages, className } = this.props
return (
<div className={className} tabIndex="-1" ref={this.setElementRef}>
<ScrollToTopOnMount />
{messages.map((message, index) => {
const purifiedContent = { __html: DOMPurify.sanitize(unescapeHtml(JSON.parse(`"${escape(window.decodeURIComponent(message.get('message')))}"`))) }
return (
<ContentEventWrapper handleContentClick={this.handleClick} key={index}>
<Message announce={true} level={message.get('level')}>
<p dangerouslySetInnerHTML={purifiedContent} />
</Message>
</ContentEventWrapper>
)
})}
</div>
)
}
}
MessageDisplayWithSpecialCharacters.propTypes = {
messages: PropTypes.array,
className: PropTypes.string,
focusOnMount: PropTypes.bool,
requestNavigation: PropTypes.func
}
const mapDispatchToProps = dispatch => ({
requestNavigation: navigationItem => dispatch(requestNavigation(navigationItem))
})
export default connect(null, mapDispatchToProps)(MessageDisplayWithSpecialCharacters)
Any help/ advice is appreciated for a novice like me.
I am trying to test a component that use context. After I mount it (shallow does not work with useContext apparently) I am trying to set default values for the component data.
I was expecting const contextValues = { text: 'mock', msg: 'SUCCESS' }; and passing that to the AlertContextProvider to set a state for that component but I am probably looking at this the wrong way.
AlertContext.js:
import React, { createContext, useState, useContext } from 'react';
export const AlertContext = createContext();
const AlertContextProvider = props => {
const [alert, setAlert] = useState({
text: '',
msg: ''
});
const updateAlert = (text, msg) => {
setAlert({
text,
msg
});
};
return (
<AlertContext.Provider value={{ alert, updateAlert }}>
{props.children}
</AlertContext.Provider>
);
};
export default AlertContextProvider;
Alert.js (component):
import React, { useContext } from 'react';
import './Alert.scss';
import { AlertContext } from '../context/AlertContext';
const Alert = () => {
const { alert } = useContext(AlertContext);
return (
<div className='alert'>
<p className="alert-para">{alert.text}</p>
</div>
);
};
export default Alert;
Alert.js(text)
import React from 'react';
import { mount } from 'enzyme';
import Alert from '../components/Alert';
import AlertContextProvider from '../context/AlertContext';
describe('Alert', () => {
let wrapper;
beforeEach(() => {
const contextValues = { text: 'mock', msg: 'SUCCESS' };
// Below mounting is needed as Enzyme does not yet support shallow mocks
wrapper = mount(
<AlertContextProvider value={contextValues}>
<Alert />
</AlertContextProvider>
);
});
test('Should render a paragraph', () => {
const element =wrapper.find('.alert-para');
expect(element.length).toBe(1); // this is correct
expect(element.text()).toEqual('mock'); // THIS FAILS AS THE VALUE OF THE ELEMENT IS AN EMPTY STRING WHILE I WAS EXPECTING 'mock'
});
});
You are passing your contextValues through value prop on <AlertContextProvider /> but you are never using that prop to initialize data inside your context provider.
In this example, I used useEffect hook as componentDidMount to initialize your state AlertContext.js`
const AlertContextProvider = props => {
const [alert, setAlert] = useState({
text: '',
msg: ''
});
// The same as component did mount
useEffect(() => {
setAlert({
text: props.value.text,
msg: props.value.msg
})
}, [])
const updateAlert = (text, msg) => {
setAlert({
text,
msg
});
};
return (
<AlertContext.Provider value={{ alert, updateAlert }}>
{props.children}
</AlertContext.Provider>
);
};
You should use useCallback hook for your updateAlert function to memoize it.
This is how my Messenger Component looks like. As you can see there is the main component and a list component. The main component is exported as default.
With this everything is working as expected in my application.
/imports/ui/components/messenger.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Container, Segment, Loader, Header } from 'semantic-ui-react'
class Messenger extends Component {
static get propTypes () {
return {
data: PropTypes.array,
articleId: PropTypes.string,
isLoading: PropTypes.bool
}
}
render () {
const { data, articleId, isLoading } = this.props
if (isLoading) { return (<Loader active inverted size='massive' className='animated fadeIn' />) }
if (articleId) { return (<MessengerList data={data} articleId={articleId} />) }
return (
<Container>
<Segment id='' className='m-b-1'>
<Header as='h1'>Title</Header>
<MessengerList data={data} />
</Segment>
</Container>
)
}
}
class MessengerList extends Component {
/* ... */
}
export default Messenger
Now I would like to do some unit testing for the main component using enzyme. This is how I am doing it, but the last test is failing as MessengerList is not defined. So how should this be done.
import React from 'react'
import { expect } from 'meteor/practicalmeteor:chai'
import { shallow } from 'enzyme'
import { Container, Loader } from 'semantic-ui-react'
import Messenger from '/imports/ui/components/messenger.jsx'
describe('<Messenger />', () => {
const defaultProps = {
data: [],
articleId: '',
isLoading: true
}
it('should show <Loader /> while loading data', () => {
const wrapper = shallow(<Messenger {...defaultProps} />);
expect(wrapper.exists()).to.be.true
expect(wrapper.find(Loader).length).to.equal(1)
})
it('should show <Container /> data has been loaded', () => {
defaultProps.isLoading = false
const wrapper = shallow(<Messenger {...defaultProps} />);
expect(wrapper.find(Container).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
})
it('should show <MessengerList /> if articleID is given', () => {
defaultProps.articleID = 'dummy'
defaultProps.isLoading = false
const wrapper = shallow(<Messenger {...defaultProps} />);
expect(wrapper.find(MessengerList).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
})
})
I do not want to change export default Messenger
Export your MessengerList class ....
export class MessengerList extends Component {
/* ... */
}
And then in the test do ....
import React from 'react'
import { expect } from 'meteor/practicalmeteor:chai'
import { shallow } from 'enzyme'
import { Container, Loader } from 'semantic-ui-react'
import Messenger, { MessengerList } from '/imports/ui/components/messenger.jsx';
describe('<Messenger />', () => {
let wrapper;
const defaultProps = {
data: [],
articleId: '',
isLoading: true
}
beforeEach(() => {
// render the component once up here in this block. It runs before each test.
wrapper = shallow(<Messenger {...defaultProps} />);
});
it('should show <Loader /> while loading data', () => {
expect(wrapper.exists()).to.be.true
expect(wrapper.find(Loader).length).to.equal(1)
});
it('should show <Container /> data has been loaded', () => {
defaultProps.isLoading = false
expect(wrapper.find(Container).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
});
it('should show <MessengerList /> if articleID is given', () => {
defaultProps.articleID = 'dummy'
defaultProps.isLoading = false
expect(wrapper.find(MessengerList).length).to.equal(1);
expect(wrapper.find(Loader).exists()).to.be.false
});
});
UPDATE
Ideally, you should state that a prop is being modified first ...
...
describe('and the data has loaded', () => {
beforeEach(() => {
defaultProps.isLoading = false;
});
it('should show <Container /> component', () => {
expect(wrapper.find(Container).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
});
});
...