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.
Related
I'm using the Redux-tool kit to set it up. We are now using #testing-library/react to set up testing-related settings.
I got a question while looking at the official document.
// test-utils.js
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
// Import your own reducer
import reducer from '../reducer'
function render(
ui,
{
initialState,
store = createStore(reducer, initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
What function does this part have in the code part above?
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
I don't know this library, but export * from '#testing-library/react' just means that anything you can import from #testing-library/react, you can now import directly from this file, test-utils.js.
I guess that they found it convenient to have a way to access just the react testing modules in one place, with the render method overwritten with their own custom version defined above.
They are basically creating their own aliased copy of the React Testing Library package where everything is the same except for the render function. This setup is explained in more detail in the testing library docs section Setup: Custom Render.
The custom render function takes the same arguments as the original render function from #testing-library/react so that they can be used interchangeably (though it adds support for extra properties initialState and store in the options object). Internally, the custom render function calls on the library's render function, which they import with an aliased name rtlRender, but it sets a default property for the wrapper option so that components will be rendered inside of a redux Provider component.
Now to the confusing exports. export * from '#testing-library/react' takes all of the exports from the testing library and re-exports them. export { render } overrides the previously exported render function with the custom one, so it needs to come after the export *.
As for why they would create the function in one place and then export it later rather than just doing export function, I think that's just a matter of code style preference. This seems to work fine, as far as I can tell:
import { render as rtlRender } from "#testing-library/react";
// re-export everything
export * from "#testing-library/react";
// override render method
export function render(somethingCustom, ui, { ...renderOptions } = {}) {
return rtlRender(ui, { ...renderOptions });
}
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.
After wrapping a React Component with the appropriate provider, the store is still not found within the jest testing environment. Is there something that I am missing, or another cleaner way to go about this?
This is the practice that is functional for other stores, and I have used with other components, so I don't see a reason why this shouldn't work. The renderer should be creating an object wrapped with the TextContext that it needs to read from in order to populate fields.
Context
import { connect } from 'react-redux';
import React, { createContext } from 'react';
export const TextContext = createContext({});
export function TextProvider({ children, text }) {
return <TextContext.Provider value={text}>{children}</TextContext.Provider>;
}
export const TextConsumer = TextContext.Consumer;
function renderComposition(props) {
const text = {
... // some text objects
};
return (
<TextProvider text={text}>
<Composition {...props} />
</TextProvider>
);
}
target failing line
beforeEach(() => {
...
subject = mount(renderer.create(renderComposition(props)));
...
)};
with error of
Invariant Violation: Could not find "store" in either the context or props of "Connect(Composition)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Composition)".
I guess your component requires mocked store, you can provide it by creating mockReduxState.js
import configureMockStore from "redux-mock-store";
export const createMockStore = state => configureMockStore()(state);
Updating the failing test by passing mockedStore.
beforeEach(() => {
...
let updatedProp = {...props, store:createMockStore};
subject = mount(renderer.create(renderComposition(updatedProp)));
...
)};
Turns out the issue was unrelated, I was importing the component rather than the connected container, so the store was never getting set. Names are half of the battle turns out. The mocking the store option is also a great way to handle this 👍 thanks paragxvii
[Create-React-App] Jest and Enzyme(3.9.0) cant seem to find my <Button/> Element from Auth.jx container..
The application should render the Auth container if(!isAuthernticated) and the button should be disabled if no input is supplied.
I tried ShallowWrapper::dive() but i get a TypeError
TypeError: SHallowWrapper::dive() can only be called on components
Auth.jsx
//...
let errorMessage = null;
let button=<Button id='Disabled' btnType="Success" disabled={false}>Disabled</Button>;
let authRedirect = null;
if (this.props.isAuthenticated) {
authRedirect = <Redirect to={this.props.authRedirectPath}/>
}
if (this.state.controls.username.value && this.state.controls.password.value){
button=<Button id='Login' btnType="Success">Login</Button>
}
return (
<div>
{authRedirect}
<form onSubmit={this.handleSubmit}>
{form}
{button}
</form>
</div>
)
}
//...
Auth.test.js
import React from 'react';
import {shallow} from 'enzyme';
import Auth from '../containers/Auth/Auth';
import Button from '../components/Button/button';
import Input from '../components/Input/input';
describe('<Auth/>',() =>{
let wrapper;
beforeEach(()=>{
wrapper=shallow(<Auth authRedirectPath='/' isAuthenticated={false}/>).dive()
})
//Test 1
it('should render disabled button if no input has been specified ',()=>{
expect(wrapper.find(Button).text()).toEqual('Disabled')
});
})
I don't believe you should be calling dive() on the wrapper in your test. Instead, you should shallow render your wrapper and then call dive() or render() on the found Button to test for its text.
So, first:
wrapper = shallow(<Auth authRedirectPath='/' isAuthenticated={false} />)
Then, when you want to find(Button) and test for its text when rendered, you would do either of the following:
expect(wrapper.find(Button).dive().text()).toEqual('Disabled')
// OR
expect(wrapper.find(Button).render().text()).toEqual('Disabled')
To demonstrate this, I've re-created a skeleton of your code here at this code sandbox. You can see, specifically in Auth.test.js how I have modified your original test with my code lines above. If you click on "Tests" in the bottom toolbar, you'll see that the test passes.
If you go into Auth.jsx and you change the username and password values - thereby affecting the Button text - then the test will fail.
My comment above has explored that you use Redux's connect HOC on the component. That's why you can't access the desired component since that's a level deeper within the tree.
I'd suggest reading my article on Medium in which you can find some details about the actual problem and also the appropriate solution.
EDIT
If you're still experiencing the same issue, I'd suggest the following:
Let's suppose that your Auth component is something like this.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export class Auth extends Component {
// Something happens here.
}
export default connect(mapStateToProps, mapDispatchToprops)(Auth);
Notice that I used the export keyword in both cases. That being said, you can test the proper component without any connection to Redux and it also reduces the generated tree.
Pay attention to import the named export class within the test file:
...
import { Auth } from './Auth';
...
I am trying to add the redux-file-upload library into a redux application.
In my component I am just adding the component exported from the lib.
I can see that the store is referred via context inside the library.
Sample code is as below,
import React, { Component } from 'react';
import { FileUpload } from 'redux-file-upload';
class Upload extends Component {
render() {
return (
<FileUpload
allowedFileTypes={['jpg', 'pdf']}
dropzoneId="fileUpload"
url="/api/path/action"
>
<button> Drag or click here
</button>
</FileUpload>
);
}
}
export default Upload;
However I get error as
Uncaught TypeError: dispatch is not a function
Any ideas? Guess it is some mistake in importing the component.
Uncaught TypeError: dispatch is not a function
I have come across this error many times for different reasons. I can't dive into the source code of that library, but it looks like that library is not able to get dispatch function from the redux store. Have you tried connecting your component to the redux store using connect() method? It's a long shot. Thought, it might work! Let me know...
UPDATE
I'm throwing related links here, hoping that you'd find the relevant piece of code.
https://github.com/reactjs/react-redux/issues/108
React with Redux? What about the 'context' issue?
https://github.com/reactjs/react-redux/issues/108
You need import 'redux-form' , your code should be like this...
import React, { Component } from 'react';
import { FileUpload } from 'redux-file-upload';
import {reduxForm} from 'redux-form';
class UploadForm extends Component {
render() {
return (
<FileUpload
allowedFileTypes={['jpg', 'pdf']}
dropzoneId="fileUpload"
url="/api/path/action"
>
<button> Drag or click here
</button>
</FileUpload>
);
}
}
export default reduxForm({
form: 'upload-form',
fields: ['fileUpload']
})(UploadForm);
Are you using a middleware with you store, like redux-thunk/redux-promise?
The library you are using requires it:
"Please note - a middleware that passes dispatch to actions, e.g.
redux-thunk, redux-promise-middleware, is required for this package to
work properly."
So if you are using react-redux and the Provider yuo can do the following:
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import promise from 'redux-promise'
const storeWithMiddleware = applyMiddleware(promise)(createStore);