I am using react-test-renderer with Jest to test react components. But if I test a react-mui modal dialog like this:
describe('Dashboard', function () {
let dashboard;
beforeEach(async () => {
testRenderer = TestRenderer.create(<MemoryRouter><Route component={Dashboard} /></MemoryRouter>);
dashboard = testRenderer.root.findByType(Dashboard);
await waitForExpect(() => expect(dashboard.instance.state.hasLoaded).toBeTruthy());
});
it('opens dialog on clicking the new class', async () => {
const button = testRenderer.root.findByType(Button);
expect(dashboard.instance.state.showDialog).toBeFalsy();
button.props.onClick();
expect(dashboard.instance.state.showDialog).toBeTruthy();
});
});
But, then I get an error:
Error: Failed: "Error: Uncaught 'Warning: An invalid container has
been provided. This may indicate that another renderer is being used
in addition to the test renderer. (For example, ReactDOM.createPortal
inside of a ReactTestRenderer tree.) This is not supported.%s'
How should I test then react portals to make this test work?
Try putting this in your tests:
beforeAll(() => {
ReactDOM.createPortal = jest.fn((element, node) => {
return element
})
});
Based on Oliver's answer, but for TypeScript users:
describe("Tests", () => {
const oldCreatePortal = ReactDOM.createPortal;
beforeAll(() => {
ReactDOM.createPortal = (node: ReactNode): ReactPortal =>
node as ReactPortal;
});
afterAll(() => {
ReactDOM.createPortal = oldCreatePortal;
});
});
For me, the existing solutions don't address the root cause.
I needed to add jest mocks for all the sub-components in the component I was testing.
For example, consider this JSX that I want to test:
import { CustomTextInput } from 'components/CustomTextInput';
import { CustomButton } from 'components/CustomButton';
return (
<>
<CustomTextInput />
<CustomButton />
</>
)
I need to add mocks for CustomTextInput and CustomButton in my test file like this:
jest.mock(
'components/CustomTextInput',
() => ({ default: 'mock-CustomTextInput' }),
);
jest.mock(
'components/CustomButton',
() => ({ default: 'mock-CustomButton' }),
);
Related
I would like add a unit test to a simple react(TS) component that embeds a tableau dashboard using the Tableau Javascript API.
An error occurs when I try to run the test file, i get the following error:
When the page renders, under the <TableauReport /> an <iframe /> will be mounted which is generated by Tableau Javascript; see logic under the useEffect
Not sure if this is an issue with react-testing-library, but I've read also it could be an issue with jsdom version; version is 16.7.0
Here is the component:
import React, { useEffect, useRef } from 'react';
import { server } from '../../constants/tableau'
interface TableauProps {
options: {
hideToolbar: boolean,
};
view: string;
ticket?: string;
}
const { tableau }: any = window;
const setVizStyle = {
width: "800px",
height: "700px",
};
const TableauReport = ({ options, view }: TableauProps) => {
const url = `${server}${view}`
const tableauRef = useRef<HTMLDivElement>(null);
useEffect(() => {
new tableau.Viz(tableauRef.current, url, options)
}, []);
return (
<div style={setVizStyle} ref={tableauRef} />
);
};
export default TableauReport;
Here is the test:
describe("Tests", () => {
let windowSpy: any;
beforeEach(() => {
windowSpy = jest.spyOn(window, "window", "get");
})
afterEach(() => {
windowSpy.mockRestore();
})
const tableauOptions = {
hideToolbar: true,
}
it('should render the component', () => {
windowSpy.mockImplementation(() => {
tableau: {
Viz: jest.fn();
}
})
render(<TableauReport options={tableauOptions} view={dashboard}/>)
})
});
Initially, I had an issue with the Viz method in the test, it was not being recognized. I had to add a spyOn to then window object and mock the implementation.
Is that the right approach to test the window.tableau?
I was wondering is there a proper way for testing an embedded tableau dashboard using react-testing-library.
Here is the tutorial I followed on how to embed a tableau dashboard using react, link
I'm new to tableau, so any help will be highly appreciated, thanks :)
I'm new to React. Somehow only my first test succeeds, and the rest fail. Even if I make the second test same as the first one, it still fails. If I comment out the first test, the second one succeeds, and then the rest fail with component not being found via the id and not present in the DOM.
Here are my tests:
describe('Given SomeComponent component', () => {
configure({ testIdAttribute: 'id' });
describe('When trying to render with only id prop set', () => {
const property = {
id: '1',
} as Property;
render(<SomeComponent prop={property} />);
it('Should render successfully', () => {
expect(screen.getAllByTestId('chart-1')).toBeTruthy();
});
});
describe('When trying to render without data prop set', () => {
const property = {
id: '1',
name: 'property of test',
} as Property;
render(<SomeComponent prop={property} />);
it('Should render successfully', () => {
expect(screen.getAllByTestId('chart-1')).toBeTruthy();
});
});
});
Tested component: SomeComponent
export interface Prop {
property: Property;
}
export const SomeComponent = ({ property }: Prop): JSX.Element => {
return (
<>
<div id={`chart-${curveProperty.id}`}>haha</div>
</>
);
};
Try this:
describe("Given SomeComponent component", () => {
configure({ testIdAttribute: "id" });
it("Should render successfully", () => {
const property = {
id: "1",
} as Property;
const { rerender } = render(<SomeComponent prop={property} />);
expect(screen.getAllByTestId("chart-1")).toBeTruthy();
const newProperty = {
id: "1",
name: "property of test",
} as Property;
rerender(<SomeComponent prop={newProperty} />);
expect(screen.getAllByTestId("chart-1")).toBeTruthy();
});
});
The solution was to use jest test instead of it and it started to work just I wanted it to.
In case switching from it to test doesn't work as it didn't for me:
If the <SomeComponent/> is slightly more complex there could be things still happening at the moment you call getAllByTestId. In my case - switching to the findAllByTestId did the job and all tests are passing as expected.
I am trying to test a simple image component which is as follows:
const ImageComponent = (props) => {
const fullPath = require(`assets/images/${props.src}`);
return <img {...props} src={fullPath} alt={props.src} />;
};
and trying to test gives Error:
Error: Cannot find module 'assets/images/BigFakeImage' from 'test/components/common/ImageComponent.test.js'
test excerpt is as follows :
const props = {
src: 'BigFakeImage',
className: 'class-name-image',
alt: 'big picture'
};
const {result} = renderHook(() => ImageComponent(props), {
wrapper: ({children}) => (
<Provider store={store}>{children}</Provider>
)
});
return result.current;
};
it('should render <img/> with defined props', () => {
jest.mock('assets/images/BigFakeImage');
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
I also tried the "moduleNameMapper" settings as specified here : https://jestjs.io/docs/webpack
But it causes the test suite to fail.
what can be done to mock the fake image and path and for the test to pass?
TIA.
I am fairly new to testing with Gatsby and Jest and I am encountering issues with my current setup.
I am trying to build a site with Gatsby and I would like for CI purposes to run unit tests with Jest and react-testing-library.
I initially was able to run a simple test:
describe('<Header>', () => {
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Header />
);
expect(container).toBeInTheDocument();
});
});
describe('click', () => {
const { container } = render(<Header />);
expect(screen.getByText('Light mode ☀')).toBeInTheDocument();
fireEvent(
getByText(container, 'Light mode ☀'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
);
expect(screen.getByText('Dark mode ☾')).toBeInTheDocument();
});
where my render is a customized one:
const AllTheProviders = ({ children }) => {
const [dark, setDark] = useState(true);
return (
<ThemeContext.Provider
value={{
dark,
toggleDark: () => setDark(!dark),
}}
>
{children}
</ThemeContext.Provider>
);
};
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
// re-export everything
export * from '#testing-library/react';
// override render method
export { customRender as render };
and the Header a simple component that consumes the context and toggles the theme from dark to light.
I since then added gatsby-theme-i18n to handle i18n and tweaked my jest config to add:
transformIgnorePatterns: ['node_modules/(?!(gatsby-theme-i18n)/)']
The Header is now wrapped in a Layout component that provides the locale and location (with the Help for Location and #reach/router that comes with Gatsby) so I can have a language toggle in the header
In the Layout:
import { Location } from '#reach/router';
[...]
<Location>
{(location) => (
<Header
siteTitle={data.site.siteMetadata.title}
location={location}
locale={locale}
/>
)}
</Location>
The Header makes use of LocalizedLink from gatsby-theme-i18n` to prefix the right locale
<li key={langKey}>
<LocalizedLink key={langKey} to={to} language={langKey}>
{langKey}
</LocalizedLink>
</li>
I also changed the test to:
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(container).toBeInTheDocument();
});
});
However, I get an error when trying to run the test:
TypeError: Cannot read property 'themeI18N' of undefined
21 |
22 | const customRender = (ui, options) =>
> 23 | render(ui, { wrapper: AllTheProviders, ...options });
| ^
specifically coming from at useLocalization (node_modules/gatsby-theme-i18n/src/hooks/use-localization.js:9:7)
where the Gatsby theme provides the i18n info through a graphQL query:
const {
themeI18N: { defaultLang, config },
} = useStaticQuery(graphql`
{
themeI18N {
defaultLang
config {
code
hrefLang
dateFormat
langDir
localName
name
}
}
}
`)
I understand that Jest does not 'understand' what to do with the i18n passed through the gatsby-theme-i18n via the Layout and if I switch my LocalizedLink to a normal Gatsby Link, the test passes (and I can console.log the container to see it's indeed a React element with the right info).
I tried a few things I saw online like using const { useLocalization } = require('gatsby-theme-i18n'); but to no avail.
Any idea how to handle this?
Should I mock the utils from gatsby-theme-i18n like other elements from Gatsby are mocked (Link etc.) following the docs?
EDIT
Thanks to the author of the library, LekoArts, and a friend a mine, I managed to make it work :)
If somebody has the same issue, you need to mock the response from the useStaticQuery of the useLocalization hook.
I have a mock file that does it:
// Libs
const React = require('react');
const gatsby = jest.requireActual('gatsby');
// Utils
const siteMetaData = require('../src/utils/siteMetaData');
module.exports = {
...gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(
// these props are invalid for an `a` tag
({
activeClassName,
activeStyle,
getProps,
innerRef,
partiallyActive,
ref,
replace,
to,
...rest
}) =>
React.createElement('a', {
...rest,
href: to,
}),
),
StaticQuery: jest.fn(),
useStaticQuery: jest.fn().mockImplementation(() => ({
site: {
siteMetadata: {
...siteMetaData,
},
},
themeI18N: {
defaultLang: 'en',
config: {
code: 'en',
hrefLang: 'en-CA',
name: 'English',
localName: 'English',
langDir: 'ltr',
dateFormat: 'MM/DD/YYYY',
},
},
})),
};
and my test:
// Libs
import React from 'react';
// Utils
import { Location } from '#reach/router';
import { render, fireEvent, getByText, screen } from './utils/test-utils';
// Component
import Header from '../header';
describe('<Header>', () => {
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(container).toBeInTheDocument();
});
});
describe('click', () => {
test('toggle language', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(screen.getByText('Light mode ☀')).toBeInTheDocument();
fireEvent(
getByText(container, 'Light mode ☀'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
);
expect(screen.getByText('Dark mode ☾')).toBeInTheDocument();
});
});
});
You can find more information here
I'm using Jest with Enzyme, and I have this component which includes a navigate method call:
export class LeadList extends React.Component {
render() {
const { navigate } = this.props.navigation;
return (
<List>
{this.props.data.allLeads.map((lead, i) => {
return (
<ListItem
key={i}
onPress={() =>
navigate('Details', lead.id)
}
/>
// ...
</ListItem>
)})}
</List>
);
}
}
I'm trying to test that it gets called properly, so I threw this together:
const testProps = props => ({
data: {
allLeads: [
{id: 1, name: 'John Doe'},
{id: 2, name: 'Jane Doe'}
],
loading: false,
},
navigation: jest.fn((options, callback) => callback('Details', 1)),
...props,
})
describe('interactions', () => {
let props
let wrapper
beforeEach(() => {
props = testProps()
wrapper = shallow(<LeadList {...props} />)
})
describe('clicking a lead', () => {
beforeEach(() => {
wrapper.find(ListItem).first().prop('onPress')
})
it('should call the navigation callback', () => {
expect(props.navigation).toHaveBeenCalledTimes(1)
})
})
})
Output is:
Expected mock function to have been called one time, but it was called zero times.
What's the right way to handle this? Do I need to use a spy?
EDIT:
I'm getting the same when I change it like so:
const testProps = props => ({
// ...
navigation: {navigate: jest.fn()},
...props,
})
it('should call the navigation callback', () => {
expect(props.navigation.navigate).toHaveBeenCalledTimes(1)
})
Output:
expect(jest.fn()).toHaveBeenCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
at Object.<anonymous> (__tests__/LeadList-test.js:48:35)
at tryCallTwo (node_modules/promise/lib/core.js:45:5)
at doResolve (node_modules/promise/lib/core.js:200:13)
at new Promise (node_modules/promise/lib/core.js:66:3)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at tryCallOne (node_modules/promise/lib/core.js:37:12)
at node_modules/promise/lib/core.js:123:15
You will need a spy to test this. Here is an example test for finding the ForgotPassword button on a LoginScreen and testing that it navigates to the correct screen.
test('Press Forgot Password Button', () => {
const spy = jest.spyOn(navigation, 'navigate')
const wrapper = shallow(
<LoginScreen
navigation={navigation}
error={{}}
onLogin={jest.fn()}
/>,
)
const forgotButton = wrapper.find('Button').at(0)
forgotButton.props().onPress()
expect(spy).toBeCalledWith('ForgotPassword')
})
The prop navigation that is passed to the component is not a function. It's an object that contains a function called navigate.
Ironically, that's exactly what you're using in your component code:
const { navigate } = this.props.navigation;
And so, you'll have to change the navigation prop that you're passing from the test to be:
navigation: {navigate: jest.fn()}
and then in your test:
expect(props.navigation.navigate).toHaveBeenCalledTimes(1)
Edit:
In order to actually get the function to be called, you'll have to simulate a press. Right now the code finds the onPress function, but doesn't invoke it.
To do this you can replace
wrapper.find(ListItem).first().prop('onPress')
with
wrapper.find(ListItem).first().props().onPress()