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