react-testing-library not cleaning up after fire event - reactjs

We have been using React hooks useReducer and useContext to handle a global store in our react app.
When running testing using react testing library we noticed that once our state is changed inside of one test all the tests that follow now have that state change.
We have attempted doing a cleanup with afterEach(cleanup) but that did not work.
Not sure what is going on?
import React, { useContext, useReducer } from 'react'
import { render, fireEvent } from '#testing-library/react'
import '#testing-library/jest-dom/extend-expect'
import TodosList from './TodosList'
import reducer from '../../reducers/reducer'
import Store from '../../context'
import fixture from '../../tests/fixtures'
function Component() {
const [state, dispatch] = useReducer(reducer, fixture)
return (
<Store.Provider value={{ state, dispatch }}>
<TodosList />
</Store.Provider>
)
}
describe('todos', () => {
it('removes a todo when button is pressed', () => {
const { getByTestId, getAllByText } = render(<Component />)
expect(getAllByText('Delete').length).toBe(3)
window.confirm = jest.fn().mockImplementation(() => true)
fireEvent.click(getByTestId('delete-1'))
expect(window.confirm).toHaveBeenCalled()
expect(getAllByText('Delete').length).toBe(2)
})
it('check that first test did not effect this test', () => {
const { getByTestId, getAllByText } = render(<Component />)
expect(getAllByText('Delete').length).toBe(3) //this fails and is 2
})
})

I have encountered same problem and I came to the conclusion that
cleanup Unmounts React trees that were mounted with render, but doesn't reset state from stores/reducers.
In my case, since I'm using another library for central state management, my solution was to create a reset function in my store and call it at the beginning of each test. You can either use the same approach which in my opinion is closer to how the user would use the app, or follow the next one.
In the official recommendation on working with Redux they recommend recreating your own render function and provide your store to it
https://testing-library.com/docs/example-react-redux
function render(
ui,
{
initialState = reducerInitialState,
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'
// In your test
const store = createStore(() => ({ count: 1000 }))
render(<Counter />, {
store,
});

Related

How to setup React-Testing-Library with Redux-RTK (in typescript)

I'm using Redux to manage the state for my components. While it works on my application, I cannot get it to work within my react-testing-library testing suite.
Good news is, Redux-RTK has documentation for setting up their library with react-testing-library here (example shown below). Bad news is, I'm having difficulty converting it from jsx to tsx and doing whatever else needs to be done to get it to actually work within the tests.
redux docs
this is the wrapper they provide to give components (within tests) access to ... the redux store/behaviors?
// testing-utils.ts
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux'
// Import your own reducer
import alertReducer from '../alertSlice'
function render(
ui,
{
preloadedState,
store = configureStore({
reducer: { alert: alertReducer },
preloadedState,
}),
...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 }
when I write a test in react-testing-library, ie user clicks a button, the value I'm testing for (shown below) appears as "undefined".
import { render, screen } from "../../../testing-utils/test-utils";
test.only("when the user clicks the 'click me' button, a snackbar appears", async () => {
expect(screen.queryByText(/header/i)).toBeInTheDocument();
const clickMeButton = screen.getByRole("button", { name: /click me/i });
user.click(clickMeButton);
expect(
// screen.debug() shows: 'undefined header' not ... 'great job header'
await screen.findByText(/great job header/i)
).toBeInTheDocument();
});
component I'm testing against:
const SelectMedia = () => {
const alertState = useAppSelector(selectAlert);
const dispatch = useAppDispatch();
const createSnackBar = () => {
dispatch(
setAlert({
type: "success",
message: "great job",
display: "support-both",
})
);
};
return (
<SelectMediaWrapper>
{alertState && alertState.message + " header"}
// in tests, returns as "undefined header"
</SelectMediaWrapper>
)
}
I'm not sure where to take it from here, the testing-utils solution is based very close to the docs and my behaviors are very similar to what they used in the redux resource. However, I can't seem to get it to work and I'm not sure what I am missing in my implementation. Any thoughts?

How to mock store in redux toolkit

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { render, screen, fireEvent } from '#testing-library/react';
import MyApp from './MyApp ';
const initialState = {};
const mockStore = configureStore(initialState);
describe('<MyApp />', () => {
it('click button and shows modal', () => {
render(
<Provider store={mockStore}>
<MyApp />
</Provider>
);
fireEvent.click(screen.getByText('ADD MIOU'));
expect(queryByText('Add MIOU Setting')).toBeInTheDocument();
});
});
I am using jest and redux toolkit with reactjs, and trying to mock a store to write a test.
But got the following error
TypeError: store.getState is not a function
Is there any way to fix this? Am I missing something?
I assume you are trying to test a connected component, and you expect (1) action creators and reducers to be run and (2) redux state to be updated as part of your test?
I have not used redux-mock-store, but I see the following note on their documentation, which leads me to believe this library may not work the way you expect:
Please note that this library is designed to test the action-related logic, not the reducer-related one. In other words, it does not update the Redux store.
I suggest you try this approach for testing connected components. I have used this approach to write tests that update redux state and render connected components.
First, you override the RTL render method:
// 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 }
Then you reference that new render method instead of RTL directly. You can also provide initial state for your test.
import React from 'react'
// We're using our own custom render function and not RTL's render
// our custom utils also re-export everything from RTL
// so we can import fireEvent and screen here as well
import { render, fireEvent, screen } from '../../test-utils'
import App from '../../containers/App'
it('Renders the connected app with initialState', () => {
render(<App />, { initialState: { user: 'Redux User' } })
expect(screen.getByText(/redux user/i)).toBeInTheDocument()
})
(All code copied from redux.js.org.)
I was having the same problem as you but thanks to #srk for linking the Redux docs and, the React Testing Library docs, I found a pretty good solution that worked for me with TypeScript:
// store.ts - just for better understanding
export const store = configureStore({
reducer: { user: userReducer },
})
export type RootState = ReturnType<typeof store.getState>
// test-utils.ts
import React, { ReactElement } from 'react'
import { Provider } from 'react-redux'
import { render as rtlRender, RenderOptions } from '#testing-library/react'
import {
configureStore,
EmptyObject,
EnhancedStore,
PreloadedState,
} from '#reduxjs/toolkit'
// import your reducers
import userReducer from 'features/user/user.slice'
import type { RootState } from 'app/store'
// ReducerTypes is just a grouping of each slice type,
// in this example i'm just passing down a User Reducer/State.
// With this, you can define the type for your store.
// The type of a configureStore() is called EnhancedStore,
// which in turn receives the store state as a generic (the same from store.getState()).
type ReducerTypes = Pick<RootState, 'user'>
type TStore = EnhancedStore<ReducerTypes>
type CustomRenderOptions = {
preloadedState?: PreloadedState<ReducerTypes & EmptyObject>
store?: TStore
} & Omit<RenderOptions, 'wrapper'>
function render(ui: ReactElement, options?: CustomRenderOptions) {
const { preloadedState } = options || {}
const store =
options?.store ||
configureStore({
reducer: {
user: userReducer,
},
preloadedState,
})
function Wrapper({ children }: { children: React.ReactNode }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...options })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
Then you just have to pass down an object with the preloadedState property as the second parameter to your render; you can even define a new store inside the render if you want with the "store" property.
describe('[Component] Home', () => {
it('User not logged', () => {
const component = render(<Home />)
expect(component.getByText(/User is: undefined/i)).toBeInTheDocument()
})
it('User logged in', () => {
const component = render(<Home />, {
preloadedState: { user: { name: 'John' /* ...other user stuff */ } },
})
expect(component.getByText(/User is: John/i)).toBeInTheDocument()
})
})
I couldn't find anywhere else to paste my findings regarding redux toolkit and redux-mock-store.
In order to dispatch async thunks and test results you can specify the type of dispatch when creating the mock store.
import configureStore from 'redux-mock-store';
import { getDefaultMiddleware } from '#reduxjs/toolkit'
const middlewares = getDefaultMiddleware();
const mockStore = createMockStore<IRootState, AppDispatch>(middlewares);
describe('my thunk action', () => {
const store = mockStore();
test('calls my service', async() => {
await store.dispatch(myThunk({ id: 32 }));
expect(myService).toBeCalledWith({ id: 32 });
});
test('contains foo bar actions', async() => {
await store.dispatch(myThunk({ id: 32 }));
expect(store.getActions()).toEqual(....);
});
});
As of January 2023 it is no longer recommended to mock the store in redux, see the docs here and this answer for more information.

Problems testing a Redux + React app with enzyme:

I have this component
import React, { useEffect } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { CircularProgress } from '#material-ui/core';
import { loadPhones } from './redux/actions/actions.js';
import TablePhones from './Table.js';
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadPhones: () => {
dispatch(loadPhones());
},
};
}
export function App(props) {
useEffect(() => {
props.loadPhones();
}, []);
if (props.phones.data) {
return (
<div className="App">
<div className="introductoryNav">Phones</div>
<TablePhones phones={props.phones.data} />
</div>
);
}
return (
<div className="gridLoadingContainer">
<CircularProgress color="secondary" iconStyle="width: 1000, height:1000" />
<p className="loadingText1">Loading...</p>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
For whom ive written
import React from 'react';
import { render } from '#testing-library/react';
import { Provider } from "react-redux";
import App from './App';
import { shallow, mount } from "enzyme";
import configureMockStore from "redux-mock-store";
const mockStore = configureMockStore();
const store = mockStore({});
describe('App comp testing', () => {
it("should render without throwing an error", () => {
const app = mount(
<Provider store={store}>
<App />
</Provider>
).dive()
expect(app.find('.introductoryNav').text()).toContain("Phones");
});
})
But that test keeps failing
ypeError: Cannot read property 'data' of undefined
I also tried importing App as {App} instead and using shallow testing, but no luck. It gives the same erros, so im left without access to the context, and I cant keep doing my tests
How can I solve this?
You could use the non-default export of your component here and shallow render test if you pass your component the props and don't try to mock the store (if I recall correctly).
I was thinking something like this might work, tesing the "pure" non-store connected version of the component. This seems to be a popular answer for this question as this was asked (in a different way) before here:
import React from 'react';
import { App } from './App';
import { shallow } from "enzyme";
// useful function that is reusable for desturcturing the returned
// wrapper and object of props inside of beforeAll etc...
const setupFunc = overrideProps => {
const props = {
phones: {
...phones, // replace with a mock example of a render of props.phones
data: {
...phoneData // replace with a mock example of a render of props.phones.data
},
},
loadPhones: jest.fn()
};
const wrapper = shallow(<App {...props} />);
return {
wrapper,
props
};
};
// this is just the way I personally write my inital describe, I find it the easiest way
// to describe the component being rendered. (alot of the things below will be opinios on test improvements as well).
describe('<App />', () => {
describe('When the component has intially rendered' () => {
beforeAll(() => {
const { props } = setupFunc();
});
it ('should call loadPhones after the component has initially rendered, () => {
expect(props.loadPhones).toHaveBeenCalled();
});
});
describe('When it renders WITH props present', () => {
// we should use describes to section our tests as per how the code is written
// 1. when it renders with the props present in the component
// 2. when it renders without the props
beforeAll(() => {
const { wrapper, props } = setupFunc();
});
// "render without throwing an error" sounds quite broad or more like
// how you would "describe" how it rendered before testing something
// inside of the render. We want to have our "it" represent what we're
// actually testing; that introductoryNave has rendered with text.
it("should render an introductoryNav with text", () => {
// toContain is a bit broad, toBe would be more specific
expect(wrapper.find('.introductoryNav').text()).toBe("Phones");
});
it("should render a TablePhones component with data from props", () => {
// iirc toEqual should work here, you might need toStrictEqual though.
expect(wrapper.find('TablePhones').prop('phones')).toEqual(props.phones);
});
});
describe('When it renders WITHOUT props present', () => {
it("should render with some loading components", () => {
expect(wrapper.find('.gridLoadingContainer').exists()).toBeTruthy();
expect(wrapper.find('CircularProgress').exists()).toBeTruthy();
expect(wrapper.find('.loadingText1').exists()).toBeTruthy();
});
});
});

Testing custom hook: Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a <Provider>

I got a custom hook which I want to test. It receives a redux store dispatch function and returns a function. In order to get the result I'm trying to do:
const { result } = renderHook(() => { useSaveAuthenticationDataToStorages(useDispatch())});
However, I get an error:
Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a
It happens because of the useDispatch and that there is no store connected. However, I don't have any component here to wrap with a provider.. I just need to test the hook which is simply saving data to a store.
How can I fix it?
The react hooks testing library docs go more into depth on this. However, what we essentially are missing is the provider which we can obtain by creating a wrapper. First we declare a component which will be our provider:
import { Provider } from 'react-redux'
const ReduxProvider = ({ children, reduxStore }) => (
<Provider store={reduxStore}>{children}</Provider>
)
then in our test we call
test("...", () => {
const store = configureStore();
const wrapper = ({ children }) => (
<ReduxProvider reduxStore={store}>{children}</ReduxProvider>
);
const { result } = renderHook(() => {
useSaveAuthenticationDataToStorages(useDispatch());
}, { wrapper });
// ... Rest of the logic
});
This is probably a late answer but you can also use this in your test
jest.mock('react-redux', () => {
const ActualReactRedux = jest.requireActual('react-redux');
return {
...ActualReactRedux,
useSelector: jest.fn().mockImplementation(() => {
return mockState;
}),
};
});
This issues is related your test file. You have to declarer provider and store in your test file.
Update or replace your app.test.tsx by below code
NB: Don't forget to install redux-mock-store if you don't have already.
import React from 'react';
import { render } from '#testing-library/react';
import App from './App';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
describe('With React Testing Library', () => {
const initialState = { output: 10 };
const mockStore = configureStore();
let store;
it('Shows "Hello world!"', () => {
store = mockStore(initialState);
const { getByText } = render(
<Provider store={store}>
<App />
</Provider>
);
expect(getByText('Hello World!')).not.toBeNull();
});
});
I got this solution after searching 1 hours.
Thanks a lot to OSTE
Original Solution: Github issues/8145 and solution link
With this solution if you get error like TypeError: window.matchMedia is not a function then solve by this way. add those line to your setupTests.ts file. Original solution link stackoverflow.com/a/64872224/5404861
global.matchMedia = global.matchMedia || function () {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
};
};
I think you can create test-utils.[j|t]s(?x), or whatever you set the name of the file to, like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/test-utils.tsx
//root(or wherever your the file)/test-utils.tsx
import React from 'react';
import { render, RenderOptions } from '#testing-library/react';
import { Provider } from 'react-redux';
// Import your store
import { store } from '#/store';
const Wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
const customRender = (ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: Wrapper, ...options });
// re-export everything
export * from '#testing-library/react';
// override render method
export { customRender as render };
Use it like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/pages/index.test.tsx
//__tests__/pages/index.test.tsx
import React from 'react';
import { render, screen } from '../test-utils';
import Home from '#/pages/index';
describe('Home Pages', () => {
test('Should be render', () => {
render(<Home />);
const getAText = screen.getByTestId('welcome');
expect(getAText).toBeInTheDocument();
});
});
Works for me.
screenshot work
BTW, if you place the test-utils.[j|t]s(?x) or whatever you set the name file place on the directory __test__, don't forget to ignore it on jest.config.js.
//jest.config.js
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/', '<rootDir>/__tests__/test-utils.tsx'],
repo: https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart

How to write a test for conditional rendering component depended on useState hook in React?

I'm trying to write a test for my functional component, but don't understand how to mock isRoomsLoaded to be true, so I could properly test my UI. How and what do I need to mock?
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchRooms } from '../../store/roomsStore'; // Action creator
// Rooms component
export default ({ match, location, history }) => {
const roomsStore = useSelector(state => state.rooms);
const dispatch = useDispatch();
const [isRoomsLoaded, setRoomsLoaded] = useState(false);
useEffect(() => {
const asyncDispatch = async () => {
await dispatch(fetchRooms());
setRoomsLoaded(true); // When data have been fetched -> render UI
};
asyncDispatch();
}, [dispatch]);
return isRoomsLoaded
? <RoomsList /> // Abstraction for UI that I want to test
: <LoadingSpinner />;
};
If you want, you could flat out mock useState to just return true or false, to get whichever result you want by doing the following.
const mockSetState = jest.fn();
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: value => [true, mockSetState],
}));
By doing this, you're effective mocking react, with react, except useState, its a bit hacky but it'll work.

Resources