I have this component:
import React from 'react';
const UploadAsync = Loadable({
loader: () => import('components/Upload'),
loading: () => <LoadingComponent full />
});
const Component = () => {
return <UploadAsync />
}
export default Component
And the test:
import React from 'react';
import Component from './index';
describe('Component', () => {
it('should render correctly with upload component', () => {
const tree = create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
});
How I can see the Upload component and not the Loading component in the snapshot?
exports[`Content should render correctly with form component 1`] = `
<div>
<Loading
full={true}
/>
</div>
`;
So far I have tried setTimeOut and Promises.
Use Loadable.preloadAll() before the tests then you can access the Component you need.
Docs
Simple example:
all imports here
Loadable.preloadAll()
describe('TestName', () => {
//tests
})
I haven't been able to figure this out either, but here are a couple of workarounds that, alone or together, may work reasonably well:
Snapshot test the components that are passed to Loadable
In your case, the test would look something like this:
import React from 'react';
import Component from './components/Upload';
describe('Component', () => {
it('should render correctly with upload component', () => {
const tree = create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
});
You could also test <LoadingComponent full /> in a similar fashion. No, this doesn't assure you that the Loadable component is working, but you may find it satisfactory to assume that the react-loadable library is well tested and will work as long as you pass to it your own, properly tested components.
End-to-end browser testing
Using a framework such as Selenium or TestCafe you can write tests that run against your site as it runs in a real browser.
It seems like there is no proper solution but if your test is actually rendering the component in browser inside iframe then you can get your react component by Jquery contentDocument
$('#component-iframe')[0].contentDocument
you can find for some specific element in your component by class or id using
$('#component-iframe')[0].contentDocument.getElementById('componentID')
I have a solution, which I found accidentally, but I don't understand how it works (maybe if we could figure out, we could solve this problem). For now, it's good for a workaround.
If you call mount once outside the test, the dynamic module will load magically:
function mountApp() {
return mount(
<ApolloProvider client={apolloClient}>
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
</ApolloProvider>
);
}
mountApp();
// Tests
test('react-loadable module loads', () => {
const wrapper = mountApp();
console.log(wrapper.debug());
});
Here, the App, which contains react-loadable modules loads correctly with all its content available. When I remove the first mountApp, it doesn't work anymore (it loads only the loading string).
Edit:
Actually it works inside the test too, but this way you only need to do this once for every test to work.
I was trying to test a component which had Loadable-components inside of it, and run into a similar problem. I managed to mock those components and that made the parent-component mount (enzyme) as I wanted it to.
I'm leaving out the mocked store and props, as they aren't relevant to the solution
const component = () => {
return mount(
<Provider store={store}>
<DoubleMatrix {...props} />
</Provider>
)
}
// These are the Loadable-components, import { Grid, GridColumn } from 'App/Components/Tables/Grid' in the parent component which I am testing
jest.mock('App/Components/Tables/Grid', () => ({
Grid: () => <div />, // eslint-disable-line
GridColumn: () => <div />, // eslint-disable-line
}))
it('renders', () => {
const wrapper = component()
expect(wrapper).toMatchSnapshot()
})
Here is how you test the loaded component:
import {LoadableFoo} from './loadable-foo';
import {Foo} from './Foo';
it('should load the Foo component', async () => {
// usual shallow render
const component = shallow(<LoadableFoo/>);
// prerender the Loadable component
const loadedComponent = await component.preload();
expect(loadedComponent.Foo).toEqual(Foo);
});
Related
I have a material-ui Snackbar component in my root pages/_app file in my project. If a suitable event happens on any component in my project, it dispatches an action to redux to update the "alert" state of my store, which triggers the Snackbar to appear.
What source of approach can I use to run tests on a component level to track that relevant (and important) toast/snackbar messages appear due to events? Is this possible within the confines of react-testing-library?
tests for a given component interacting with snackbar
import { render } from "#testing-library/react";
import user from "#testing-library/user-event";
describe("if the url is invalid", () => {
beforeEach(() => {
render(<SelectMedia />);
user.click(screen.getByRole("button", { name: /search/i }));
});
test("the VideoPlayer does not activate", () => {
expect(screen.getByTestId())
});
test.todo("a warning toast message appears");
// ^ what can I do to make tests like these work?
});
root app
const myApp = () => {
...
return (
<Provider store={store}>
<StyledSnackBar />
<Component {...pageProps} />
// ^ all components (like SelectMedia) exist within <Component ... />
</Provider>
)
}
toast/snackbar does not exist within the <SelectMedia /> component
toast/snackbar exists on a root page as a sibling and uses redux to trigger behavior
Here is a sample of my code
// App.js
import React, { Suspense, lazy } from "react";
const Loader = () => <div data-testid="loader">Loading...</div>
const Login = lazy(() => import("./Login"));
function App() {
return (
<Suspense fallback={<Loader />}>
<Login />
</Suspense>
);
}
export default App;
// Login.js
import React from 'react';
const Login = () => <div data-testid="login">Login</div>
export default Login
// App.test.js
import React from 'react';
import { render, waitForElementToBeRemoved, cleanup } from '#testing-library/react';
import App from './App';
describe('App when user is not signed in', () => {
it("should redirect to login page", async () => {
beforeEach(() => jest.resetAllMocks())
const { getByTestId, getByText } = render(<App />);
await waitForElementToBeRemoved(() => getByTestId('loader'))
const linkElement = getByText(/Login/i);
expect(linkElement).toBeInTheDocument();
});
})
describe('App with User Logged in as Admin', () => {
it("redirect to login page", async () => {
beforeEach(() => {
// will set local storage for auth token
// for a logged in user
})
let container = document.createElement('div')
const { getByTestId, getByText } = render(<App />, {
container
});
await waitForElementToBeRemoved(() => getByTestId('loader'))
const linkElement = getByText(/Login/i);
expect(linkElement).toBeInTheDocument();
});
})
The issue I am having is that I expect to have the loader when I run the second test but it is not there thus throwing an error.
I would like to know why the loader is not rendered in the second test which renders the login page straight away. I suspect the first test is affecting the second test but the question is why.
I created a repl to this issue here. https://repl.it/#tibetegya/Create-React-App
toBeInTheDocument asserts that the element exists in the document, as the name suggests:
The jest-dom utility library provides the .toBeInTheDocument() matcher, which can be used to assert that an element is in the body of the document, or not. This can be more meaningful than asserting a query result is null.
The component was rendered to detached container element in the second test and doesn't exist in document DOM, so the test fails.
Instead, basic Jest assertions can be used, as the documentation suggests:
expect(linkElement).not.toBeNull()
There may be no reason to use custom container, unless there's a need to test how the component works in detached elements.
// MyComponent.jsx
const MyComponent = (props) => {
const { fetchSomeData } = props;
useEffect(()=> {
fetchSomeData();
}, []);
return (
// Some other components here
)
};
// MyComponent.react.test.jsx
...
describe('MyComponent', () => {
test('useEffect', () => {
const props = {
fetchSomeData: jest.fn(),
};
const wrapper = shallow(<MyComponent {...props} />);
// THIS DOES NOT WORK, HOW CAN I FIX IT?
expect(props.fetchSomeData).toHaveBeenCalled();
});
});
When running the tests I get:
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
The expect fails because shallow does not call useEffect. I cannot use mount because of other issues, need to find a way to make it work using shallow.
useEffect is not supported by Enzyme's shallow rendering. It is on the roadmap (see column 'v16.8+: Hooks') to be fixed for the next version of Enzyme, as mentioned by ljharb
What you're asking is not possible with the current setup. However, a lot of people are struggling with this.
I've solved / worked around this by:
not using shallow rendering from Enzyme anymore
use the React Testing Library instead of Enzyme
mocking out modules via Jest
Here's a summary on how to mock modules, based on Mock Modules from the React docs.
contact.js
import React from "react";
import Map from "./map";
function Contact(props) {
return (
<div>
<p>
Contact us via foo#bar.com
</p>
<Map center={props.center} />
</div>
);
}
contact.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Contact from "./contact";
import MockedMap from "./map";
jest.mock("./map", () => {
return function DummyMap(props) {
return (
<p>A dummy map.</p>
);
};
});
it("should render contact information", () => {
const center = { lat: 0, long: 0 };
act(() => {
render(
<Contact
name="Joni Baez"
email="test#example.com"
site="http://test.com"
center={center}
/>,
container
);
});
});
Useful resources:
React Testing Library docs - mocking
React docs - Mocking Modules
Here's a solution from a colleague of mine at CarbonFive:
https://blog.carbonfive.com/2019/08/05/shallow-testing-hooks-with-enzyme/
TL;DR: jest.spyOn(React, 'useEffect').mockImplementation(f => f())
shallow doesn't run effect hooks in React by default (it works in mount though) but you could use jest-react-hooks-shallow to enable the useEffect and useLayoutEffect hooks while shallow mounting in enzyme.
Then testing is pretty straightforward and even your test specs will pass.
Here is a link to a article where testing the use-effect hook has been clearly tackled with shallow mounting in enzyme
https://medium.com/geekculture/testing-useeffect-and-redux-hooks-using-enzyme-4539ae3cb545
So basically with jest-react-hooks-shallow for a component like
const ComponentWithHooks = () => {
const [text, setText] = useState<>();
const [buttonClicked, setButtonClicked] = useState<boolean>(false);
useEffect(() => setText(
`Button clicked: ${buttonClicked.toString()}`),
[buttonClicked]
);
return (
<div>
<div>{text}</div>
<button onClick={() => setButtonClicked(true)}>Click me</button>
</div>
);
};
you'd write tests like
test('Renders default message and updates it on clicking a button', () => {
const component = shallow(<App />);
expect(component.text()).toContain('Button clicked: false');
component.find('button').simulate('click');
expect(component.text()).toContain('Button clicked: true');
});
I'm following this advice and using mount() instead of shallow(). Obviously, that comes with a performance penalty, so mocking of children is advised.
I'm trying to determine how best to write unit tests when using dynamic imports using React Universal Component 2.0
https://github.com/faceyspacey/react-universal-component
TestableComponent is a component I want to test. I want to test that "ChildComp" is returned correctly. In reality there's a lot of logic and transformation involved but as the base case, I just to be able to test that "ChildComp" exists. I'm using Universal Component to do a dynamic import of the "ChildComp"
TestableComponent.js
import React, { Component } from 'react'
import universal from 'react-universal-component'
const ChildComp = universal(() => import(/* webpackChunkName: 'child' */ 'common/ChildComp'), {
resolve: () => require.resolveWeak('common/ChildComp'),
chunkName: 'child'
})
class TestableComponent extends Component {
constructor (props) {
super(props)
this.childNodes = []
}
componentWillMount () {
this.childNodes.push(<ChildComp id='myLink' key='myLink' />)
}
render () {
return (
<div>{this.childNodes}</div>
)
}
}
export default TestableComponent
TestableComponent Unit Test
import React from 'react'
import TestableComponent from '../TestableComponent'
import { mount, shallow } from 'enzyme'
const waitFor = ms => new Promise(resolve => setTimeout(resolve, ms))
describe('Testable Component test', () => {
it('tests transformation', async () => {
const compMount = mount((<TestableComponent />))
console.log(compMount.debug())
/* output: <TestableComponent >
<div>
<UniversalComponent id="myLink">
<DefaultLoading id="myLink">
<div>
Loading...
</div>
</DefaultLoading>
</UniversalComponent>
</div>
</TestableComponent> */
const compMountWait = mount((<TestableComponent />))
await waitFor(1000) // dynamic import
console.log(compMountWait.debug())
/* output: <TestableComponent>
<div>
<UniversalComponent id="myLink">
<ChildComp id="myLink" />
</UniversalComponent>
</div>
</TestableComponent> */
})
})
Notice in the first debug() that initially ChildComp isn't shown. Just loading information that importing of the components aren't complete yet.
After the waitFor(1000), you can see that ChildComp is available.
Question: Is it proper to use a timeout to let dyanmic imports to complete before structural testing, or is there a programmatic way to determine when dynamic imports are complete?
I implemented a solution using the mock in Jest to be able to override the default behavior of react-universal-component and add the call to the preload function in all components. You just have to use this code every test, so I suggest putting it in the test setup file. This means you don't have to change anything in your code and allows you to test your components without waiting for X seconds.
Mock Solution:
jest.mock('react-universal-component', () => ({
__esModule: true,
default: (spec, options) => {
const universal = jest.
requireActual('react-universal-component').
default; // Get original default behavior from the library.
const UniversalComponent = universal(spec, options); // Call original function.
UniversalComponent.preload(); // Preload component.
return UniversalComponent; // Return loaded Universal Component.
},
}));
Your Code Remains the Same:
const ChildComp = universal(() => import(/* webpackChunkName: 'child' */
'common/ChildComp'), {
resolve: () => require.resolveWeak('common/ChildComp'),
chunkName: 'child'
})
I am using the library React-Reponsive.
https://github.com/contra/react-responsive
I am struggling to figure out how to test components that are nested in React-Responsive Media Query Components:
export default class AppContainer extends React.Component {
render(){
return(
<MediaQuery minDeviceWidth={750}>
<Header />
</MediaQuery>
);
}
}
-
describe("AppContainer", () => {
let App;
let wrapper;
beforeEach(() => {
wrapper = mount(<Provider store={store}><AppContainer location={location} /></Provider>);
App = wrapper.find(AppContainer);
});
it('to have a <Header /> component', () => {
console.log(App.debug());
expect(App.find(Header)).to.have.length(1);
});
}
The test result:
1) AppContainer to have a <Header /> component:
AssertionError: expected { Object (component, root, ...) } to have a length of 1 but got 0
The relevant part of the output of the console.log is:
<MediaQuery minDeviceWidth={750} values={{...}} />
Indicating that Header is indeed not appearing in the render tree. However if I remove the MediaQuery and make Header a direct child of AppContainer the test passes.
I imagine this is not a bug as I'm very new to Enzyme and testing components in general. Any help or examples would be appreciated.
Please note: The other tests I have on this component are passing fine. I am confident that the imports and setup are all correct.
Issue was that Media Query is looking for window.matchMedia which with jsdom is undefined.
In this case I needed to use the Server Side Rendering implementation. However this would require a static value for width, which breaks the responsiveness.
My solution is to set a global variable on the test virtual DOM.
window.testMediaQueryValues = {width:740};
Then MediaQuery can access them if they are there:
<MediaQuery maxWidth={smallViewMaxWidth} values={window.testMediaQueryValues}>
In the case when the variable is not set, the null values are ignored and the Component renders as usual.
Big thanks to #Contra for his help and super library
What worked for me was adding a mocked react-responsive component using a __mocks__ directory. Basically create the following file in the directory structure:
-your-component
--component-using-media-query.js
--__mocks__
---react-responsive.js
Then you can mock out the MediaQuery component in the react-responsive.js file.
const MediaQuery = ({ children }) => children;
export default MediaQuery;
I was able to make it work using react-responsive v7.0.0.
Given:
<MediaQuery minDeviceWidth={750}>
<Header />
</MediaQuery>
The following works:
import { Context as ResponsiveContext } from 'react-responsive'
import { mount } from 'enzyme'
const wrappingComponent = ResponsiveContext.Provider
const wrappingComponentProps = { value: { width: 750 } }
const mountProps = { wrappingComponent, wrappingComponentProps }
const wrapper = mount(<AppContainer />, mountProps)
Try mocking the react-responsive dependency in your unit test. Working with Webpack, I'm using inject-loader for injecting test dependencies to modules. You can import your component using "inject-loader" and pass it which dependencies you want to overwrite.
Example:
import YourComponentInjector from 'inject-loader!../YourComponent.jsx';
and then
class MediaQueryDesktopMock extends React.Component {
render() {
const {minDeviceWidth, children} = this.props;
if(minDeviceWidth === 765) {
return (<div>{children}</div>)
}
return <span/>;
}
}
let YourComponentMock = YourComponentInjector({
'react-responsive': MediaQueryDesktopMock
});
you can then test for specific media queries