Access redux store in react test - reactjs

I have the following test
import React from 'react';
import { render } from '#testing-library/react';
import { Provider } from 'react-redux';
import {store} from '../../app/store';
import Game from './Game';
test('should render Game component correctly', () => {
const { getByText } = render(
<Provider store={store}>
<Game/>
</Provider>
);
});
I would like to make some assertions about the Game component based on the state in the redux store, but how do I access the state from within my test?

Try wrapping your tests in a describe function, like so:
let store;
describe("Your test", () => {
test('should render Game component correctly', async () => {
const { getByText } = render(
<Provider store={store}>
<Game />
</Provider>
);
await findByText('This text is now visible because your state was updated by the reducer');
});
});
Inside, add a beforeEach statement above your tests:
beforeEach(() => {
store = createTestStore();
});
Implement your createTestStore function:
export function createTestStore() {
const store = createStore(
combineReducers({
// The reducers you use
example: exampleReducer,
})
);
return store;
}
I wrote this answer based on an article by Phil Lucks in Medium. Here is a link if you want to check it yourself!

Related

React Testing Library / Redux - How to mock cookies?

Issue
I'm able to mock the cookie in Jest, but it won't change the state of my components once the cookie is mocked.
For example, when a user visits my app, I want to check if a cookie of ACCESS_TOKEN exists, if it exists, render a saying "Hi, Username".
When testing, I'm able to create the cookie and get the values with console.log(), but my component won't render the because the test does not think redux-store has the cookie.
Here's what my redux-store looks like (Redux store is not the problem, all my tests that does not rely on cookies and soley relies on store are working):
Root.tsx
export const store = createStore(
reducers,
{ authStatus: { authenticated: Cookies.get("ACCESS_TOKEN") } },
//if our inital state (authStauts) has a cookie, keep them logged in
composeWithDevTools(applyMiddleware(reduxThunk))
);
const provider = ({ initialState = {}, children }) => {
return <Provider store={store}>{children}</Provider>;
};
export default provider
App.tsx
import Root from "./Root"; //root is provider variable in Root.tsx
ReactDOM.render(
<React.StrictMode>
<Root>
<App />
</Root>
</React.StrictMode>,
document.getElementById("root")
);
Welcome.tsx
const Welcome =(props) => {
return(
<div>
{props.authStatus && <h3> Hello USERNAME</h3>}
</div>
}
}
const mapStateToProps = (state) => {
return {
authStatus: state.authStatus.authenticated,
};
};
export default connect(mapStateToProps, {})(Welcome);
Here's my test:
import Cookies from "js-cookie"
beforeEach(async () => {
//Before the component renders, create a cookie of ACCESS_TOKEN.
//Method 1 (Works, console.log() below would show the value, but component won't render):
//jest.mock("js-cookie", () => ({ get: () => "fr" }));
//Method 2 (Works, console.log() below would show the value, but component won't render):
//Cookies.get = jest.fn().mockImplementation(() => "ACCESS_TOKEN");
//Method 3 (Works, console.log() below would show the value, but component won't render)):
// Object.defineProperty(window.document, "cookie", {
// writable: true,
// value: "myCookie=omnomnom",
// });
app = render(
<Root>
<MemoryRouter initialEntries={["/"]} initialIndex={0}>
<Routes />
</MemoryRouter>
</Root>
);
console.log("Cookie Val", Cookies.get());
app.debug(); //I want to see that the <h3> is rendered, but it's not being rendered.
});
Why is this occurring?
Resources used:
How to mock Cookie.get('language') in JEST
Use Jest to test secure cookie value
I'm not exactly sure how did you combine things together but I'm gonna drop you a full example that you can follow and fix your code as bellow, please check inline comments:
// Provider.jsx
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Cookies from "js-cookie";
// the reducer I assume as same as you created
const authReducer = (state = {}, action) => {
return {
...state,
...action.payload,
}
}
const store = createStore(
authReducer,
{ authStatus: { authenticated: Cookies.get("ACCESS_TOKEN") } }
);
export default ({ initialState = {}, children }) => {
return <Provider store={store}>{children}</Provider>;
};
// Routes.jsx
// should be the same as you did
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Welcome from "./Welcome";
export default (props) => {
return (
<Switch>
<Route exact path="/" component={Welcome} />
</Switch>
)
}
Finally the test file:
// index.test.js
import React from 'react';
import Cookies from "js-cookie"
import "#testing-library/jest-dom"
import { screen, render } from '#testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import Routes from "./Routes";
import Provider from "./Provider";
// Mock your cookie as same you did
// but should be the same level with `import` things
jest.mock("js-cookie", () => ({ get: () => "fr" }), {
// this just for being lazy to install the module :)
virtual: true
});
it('should pass', () => {
render(
<Provider>
<MemoryRouter initialEntries={["/"]} initialIndex={0}>
<Routes />
</MemoryRouter>
</Provider>
);
expect(screen.queryByText('Hello USERNAME')).toBeInTheDocument()
})
PS: The link I created the test for you: https://repl.it/#tmhao2005/js-cra (ref at src/Redux to see full example)

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();
});
});
});

Shallow copy a React component with a provided store in Enzyme

When I try shallow(<LoginForm />) I get the following error Invariant Violation: Could not find react-redux context value; please ensure the component is wrapped in a <Provider>. So in order to fix this, I tried:
const wrapper = shallow(
<Provider store={store}>
<LoginForm />
</Provider>
);
This works, however, the debug output is:
<ContextProvider value={{...}}>
<LoginForm />
</ContextProvider>
But I also want to render the LoginForm. Some other things I tried to get this fixed:
wrapper.find(LoginForm).shallow();
shallow(
<Provider store={store}>
<LoginForm />
</Provider>
).dive();
wrapper.find(LoginForm).shallow();
shallow(<LoginForm />, {
wrappingComponent: Provider,
wrappingComponentProps: { store }
});
But all of these result in the same error mentioned above. How am I able to fix this, while using the shallow method? Also, the LoginForm uses react hooks, including a useSelect hook, so passing a store to my component prop is not the solution I'm looking for.
You can mock useSelector, also handy mock selector function
import React from 'react';
import { mount, shallow } from 'enzyme';
import { getIsAuthorized } from 'modules/auth/reducer';
import SignIn from '../SignIn';
jest.mock('modules/auth/reducer');
jest.mock('react-redux', () => {
const RealModule = jest.requireActual('react-redux');
return {
...RealModule,
useSelector: (fn) => fn(),
};
});
interface SetupProp {
isAuthorized: boolean;
}
describe('Page: SignIn', () => {
const setupWrapper = ({ isAuthorized }: SetupProp) => {
(getIsAuthorized as jest.Mock).mockReturnValue(isAuthorized);
return shallow(<SignIn />);
};
test('should render form', () => {
const wrapper = setupWrapper({ isAuthorized: false });
expect(wrapper).toMatchSnapshot();
});
});
SignIn Component:
const SignIn: FunctionComponent = () => {
//...
const isAuthorized = useSelector(getIsAuthorized);
//...
}

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

Why enzyme tests did not work in React.js?

I am using Enzyme tests within Create-React-App. In shallow rendering it works fine, but mount rendering throws error:
TypeError: Cannot read property 'favorites' of undefined
Test file looks like this:
import React, { Component } from "react";
import configureMockStore from "redux-mock-store";
import { shallow, mount } from "enzyme";
import { Provider } from "react-redux";
import Favorites from "../Landing/Favorites";
const mockStore = configureMockStore();
const store = mockStore({});
function setup() {
const props = {
favorites: 42
};
const wrapper = mount(
<Provider store={store}>
<Favorites {...props} />
</Provider>
);
return {
props,
wrapper
};
}
describe("Favorites component", () => {
const { wrapper } = setup();
it("should render list of favorites cards", () => {
expect(wrapper.prop("favorites")).toEqual(42);
});
});
Why did it happen?
.prop works different in mount and shallow. You can check the docs.
http://airbnb.io/enzyme/docs/api/ReactWrapper/prop.html
http://airbnb.io/enzyme/docs/api/ShallowWrapper/prop.html
When using mount, you can directly render Favorites component.
mount(<Favorites {...props} />)

Resources