How to test a component which is built with styled component - reactjs

I'm new to React testing and with Jest and Enzyme.
I'm trying to learn how to use a TDD approach first and due to that, I'm building my tests before starting coding.
What I did was to create a sample app in React and I installed Enzyme dependencies and then I wrote the test:
import { shallow } from "enzyme";
import React from "react";
import AppLayout from "./AppLayout";
import { ContentLayout } from "./styles";
it("renders <AppLayout /> component", () => {
const wrapper = shallow(<AppLayout />);
expect(wrapper.find(ContentLayout)).to.have.lengthOf(1);
});
Then I built the component which contains a styled component called ContentLayout
import React from "react";
import { ContentLayout } from "./styles";
const AppLayout = () => {
return (
<>
<ContentLayout>
<h1>HELLO</h1>
</ContentLayout>
</>
);
};
export default AppLayout;
I'm unable yo make the test pass as what I got was the next error:
TypeError: Cannot read property 'have' of undefined
I would like to learn how shoulæd be the practice to test this kind of component and what rules to follow in general when I start a project from scratch with TDD in mind.
The AppLayout is called then in App.js
import React from "react";
import AppLayout from "./Components/AppLayout";
function App() {
return <AppLayout />;
}
export default App;

You should use .toHaveLength(number) matchers of expect in jestjs.
expect(wrapper.find(ContentLayout)).toHaveLength(1);
For nested components, there are two strategies generally:
Shallow Rendering API
Shallow rendering is useful to constrain yourself to test a component as a unit, and to ensure that your tests aren't indirectly asserting the behavior of child components.
This means we don't want to render the nested component(ContentLayout), we only test the behavior(lifecycle methods, event handlers, data fetching, condition render, etc.) of the parent component(AppLayout).
Full Rendering API (mount(...))
Full DOM rendering is ideal for use cases where you have components that may interact with DOM APIs or need to test components that are wrapped in higher order components.

Related

Testing a react component using useSelector hook

I am having a lot of trouble trying to implement tests for a component using the useSelector hook from react redux. I've seen some questions already about this subject but I didn't manage to fix my problem using the suggested solutions to those questions.
My component is pretty big so I won't post it all but the part giving me trouble looks like this :
Total.tsx
import React from 'react';
import clsx from 'clsx';
import i18next from 'i18next';
import { useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import Box from '#material-ui/core/Box';
import CustomTooltip from '../CustomTooltip/CustomTooltip';
import SkeletonTotal from 'components/Skeletons/Total';
import { ApplicationHelper } from 'helpers';
import './Total.scss';
//Some interfaces here for types since this is in TypeScript
function Total(props: TotalProps) {
const { currency } = useSelector(
(state: { currencyReducer: any }) => state.currencyReducer
);
...
}
I first tried to test it like another component that doesn't use redux like so :
Total.test.js (first attempt)
import React from 'react';
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
);
});
But I was getting an error saying I need a react-redux context value and to wrap my component in a Provider which led me to try this :
Total.test.js (attempt 2)
import React from 'react';
import { Provider } from 'react-redux'
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Provider>
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
</Provider>
);
});
I am now getting a "Cannot read property 'getState' of undefined" error for the Provider component. I did try to mock a store to pass to my Provider as well as using jest to mock a return value like so
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({currency: 'cad'})
Unfortunately I was unsuccessful to make this work and could not find a working solution in the other questions that might relate to this. Any ideas how I could make this work? Thanks
The useSelector hook relies on the redux Context in order to access the state, so it must be inside of a Provider component in order to work. Your second attempt is on the right track, but you haven't set the store prop on the Provider, so the store is undefined and you get error "Cannot read property 'getState' of undefined".
Since you'll likely have many components that you'll want to test with redux context, the redux docs suggest creating your own version of the react testing library's render function which wraps the element in a provider before rendering it. This new render function adds two new optional options to the standard RTL options: initialState and store.
You can basically copy and paste that entire test-utils.js example from the docs, but I modified the return to include the created store so that we can dispatch to it directly (rather than just interacting with the component in ways that will dispatch an action).
return {
...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }),
store
};
With typescript annotations.
Inside your component test file, you will use your test-utils to render the Total component. It's fine to return the container element but you don't actually need to because you can query matching elements on the global RTL screen object or on the bound queries for your base element. We are basically looking to see that the outputted HTML code matches the expectations. You could test the selector itself in isolation, but it seems like you are trying to test the component.
Your test might look something like this:
import React from "react";
import Total from "./Total";
import { render, screen } from "./test-utils";
// if you want events: import userEvent from "#testing-library/user-event";
test( 'gets currency from redux', () => {
// render with an initial currency
const { store, container, getByLabelText } = render(
// not sure where these props come from, presumable constants in the file
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />,
{ initialState: { currency: USD } }
);
// some sort of RTL matcher or document.querySelector
const currencyElement = getByLabelText(/currency/i); // uses regex for case-insensitivity
// some sort of check for value
expect(currencyElement?.innerText).toBe("USD");
// dispatch an action to change the currency
// might want to wrap in `act`?
store.dispatch(setCurrency("EUR"));
// check that the value changed
expect(currencyElement?.innerText).toBe("EUR");
});
Working example that I created based on a basic counter component.

Testing react component that is dependent on Stateful Context Providers chain in React Testing Library

I'm aware of this, but it's not my case.
Example:
<AuthProvider>
<SessionProvider>
<AnotherProvider>
<OneMoreProvider>
<MyComponent />
All of these providers are actually regular React Components with state and effects, that fetch some data via GraphQL and pass that data as a 'value' prop to MyContext.Provider in return statement.
This enforces me to create lots of mocks for modules that are being used in all of these providers, just to render my own component in testing env.
Any thoughts about what can be done to avoid creating so many mocks?
You can create a helper test lib with a custom render function that wrap your component with the contexts then export all react testing library methods from there
- test/lib.jsx
import React from 'react';
import { render as reactRender } from '#testing-library/react';
export * from '#testing-library/react';
export const render = (MyComponent, options) => {
return reactRender(
<AuthProvider>
<SessionProvider>
<AnotherProvider>
<OneMoreProvider>
{MyComponent}
</OneMoreProvider>
</AnotherProvider>
</SessionProvider>
</AuthProvider>,
options
)
}
Then use this helper lib to import test functions instead of using #testing-library/react directly
import { render } from 'test/lib'
import MyComponent from './MyComponent';
test("My component", () => {
const { getByTestId, ... } = render(<MyComponent>);
...
});

ReactWrapper::state() can only be called on class components Unit Testing Jest and Enzyme

Writing unit testing in react using jest and enzyme. While checking with a component state , it throws an error "ReactWrapper::state() can only be called on class components ".
import React from 'react';
import { mount } from 'enzyme';
import expect from 'expect';
import CustomerAdd from '../CustomerAdd'
import MUITheme from '../../../../Utilities/MUITheme';
import { ThemeProvider } from '#material-ui/styles';
describe('<CustomerAdd />', () => {
const wrapper = mount(
<ThemeProvider theme={MUITheme}>
<CustomerAdd {...mockProps}></CustomerAdd>
</ThemeProvider>
);
test('something', () => {
expect(wrapper.find(CustomerAdd).state('addNewOnSubmit')).toEqual(true);
});
});
In the above code CustomerAdd Component is class component.I don't what wrong with my code. Can any one help me out of this problem. Thanks in advance.
So your default export
export default withStyles(styles)(CustomerAdd);
exports functional(HOC) wrapper about your class-based component. And it does not matter if name of class and import in
import CustomerAdd from '../CustomerAdd'
are equal. Your test imports wrapped version and after calling .find(CustomerAdd) returns that HOC not your class. And you're unable to work with instance.
Short time solution: export class directly as named export.
export class CustomerAdd extends React.Component{
...
}
export default withStyles(styles)(CustomerAdd);
Use named import in your tests:
import { CustomerAdd } from '../CusomerAdd';
Quick'n'dirty solution: use .dive to access your underlying class-based component:
expect(wrapper.find(CustomerAdd).dive().state('addNewOnSubmit')).toEqual(true);
It's rather antipattern since if you add any additional HOC in your default export you will need to monkey-patch all related tests with adding appropriate amount of .dive().dive()....dive() calls.
Long-term solution: avoid testing state, it's implementation details.
Instead focus on validating what's been rendered. Then you are safe in case of lot of different refactoring technics like replacing class with functional component, renaming state/instance members, lifting state up, connecting component to Redux etc.

Shallow Rendering Jest Testing with Material UI withStyles Component

I am writing a test for a component that is wrapped in a withStyles() from Material UI using Jest. I need to test the children elements, but my wrapper is undefined.
I've seen another post similar to writing tests with withStyles(), but the undefined error still persists.
Test File:
import { shallow } from 'enzyme';
import { TempComponent } from '../../../../src/components/helpers/temp';
describe('temp', () => {
let wrapper;
const renderComponent = () => shallow(<TempComponent />);
beforeEach(() => {
wrapper = renderComponent();
});
it('render correctly', () => {
expect(wrapper.type()).toEqual('div');
});
});
Component:
import React from 'react';
import { withStyles } from '#material-ui/core/styles';
const TempComponent = () => <button>Click Me!</button>;
export default withStyles({})(TempComponent);
I get this error for my test:
Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was undefined.
I am wanting the wrapper component to behave the same way as a wrapper without the withStyles() component. Any help would be appreciated!
Note: I am not doing snapchat testing with jest
This appears to be the issue and solution:
https://github.com/mui-org/material-ui/issues/9266#issuecomment-349447137
The issue isn't about the children elements but with the intermediary
element, it's creating. The shallow() API of enzyme only render one
level depth. Any higher-order component is going to obfuscate the
children. You have different workarounds. I would encourage you to
refer to the enzyme documentation. Still, here they are:
use mount()
use .dive()
use our createShallow() public API
use our internal until() helper

Testing components that contain react router Link with Enzyme

I am using enzyme. I need to test a component that has react router Link as a child. I need the following
mount the component using mount() of enzyme since I need to test the whole component tree
test behaviours of component when it properties change.
I cannot wrap my component with StaticRouter or MemoryRouter since enzyme only allows setProps() at root level.
My current solution is to stub the Link render method with sinon. Here is a short example.
import {Link} from 'react-router-dom';
import sinon from 'sinon';
// ....
// ....
describe('test',() => {
before(() => {
sinon.stub(Link.prototype, 'render').callsFake(function reactRouterLinkRender() {
const {innerRef, replace, to } = this.props;
const _props = {href: to, ref: innerRef, replace, onClick: this.handleClick};
return (<a {..._props}>this.props.children</a>);
});
});
});
Is there a better way to avoid the error "Invariant Violation: You should not use Link outside a Router"?
Thanks
Not sure if this helps anyone but for my case I managed to get around by shallow rendering the top level component and then using dive() to get to the parts I required.
loginPage = shallow(<LoginPage />);
....
loginPage.find(LoginForm).dive().find({name:"user"}).simulate("change", userEventMock);

Resources