I am trying to understand the process of react and redux testing, i am using testing library to use the dom node queries for testing my project, but i am still confused of the way i should test the redux implementations in my react project:
I created a custom render function instead of the normal render method from react testing library
import React from 'react'
import { render as rtlRender } from '@testing-library/react'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares);
//test-utils.js
//creating a custom render function so we can wrap our App with
//react-redux provider
const render = (ui, initialState) => {
const store = mockStore(initialState);
//App wrapper and mocked store passed to it
const Wrapper = ({ children }) => {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper })
}
// re-export everything
export * from '@testing-library/react'
// override render method
export { render }
and in App.test.js, i am manipulating the initialState manually .this is part of confusing i don't know if i am doing right here:
describe('App', () => {
const { getByText, getByTestId, findByText, queryByText } = screen;
let initialState = {
data: {
books: [],
error: '',
loading: false
},
//rest of the state
}
it('should render App correctly with given redux state', () => {
const { container } = render(<App />, initialState);
expect(container.firstChild).toMatchSnapshot();
expect(getByTestId(/header/)).toHaveTextContent('React Testing')
});
it('displays loading message before data get fetched', () => {
initialState = {
...initialState,
data: {
...initialState.data,
loading: true
}
}
render(<App />, initialState);
expect(getByText(/...loading books/)).toBeInTheDocument();
});
it('display an error message if any thing wrong happened while fetching data', () => {
initialState = {
...initialState,
data: {
...initialState.data,
error: 'something went wrong'
}
}
render(<App />, initialState);
expect(getByText(/something went wrong/)).toBeInTheDocument();
})
})
This is for example the action creator that i am calling in App component
export const fetchData = () => dispatch => {
dispatch({ type: SET_LOADING }); // this set loading to true
return axios.get("https://api.jsonbin.io/b/57d5760ea")
.then(res => {
dispatch({
type: FETCH_DATA, // this set data
payload: res.data.books
});
dispatch({ type: STOP_LOADING })
})
.catch(err => {
dispatch({
type: SET_ERROR, // this set errors
payload: 'Something went wrong'
})
})
}
And this is App.js component:
function App({ fetchData, data: { loading, error, books } }) {
useEffect(() => {
fetchData()
}, []);
return (
<div className="App">
<header data-testid="header">
<h2>React Testing</h2>
<Bag />
</header>
{
error ? error :
!loading ? <Bookstore books={books} /> : <span data-testid='loading-message'>...loading books</span>
}
</div>
);
}
const mapStateToProps = state => ({
data: state.data,
});
I am not sure if using the initialState like this is a right way to do that as i didn't find any other way to implement in my test cases, and i experienced the problem when i tried to test if the loading message will disappear after data fetched using waitForElementToBeRemoved
as i always get timeout error indicating loading
never get to false as in the actual app!
Is using initialState like this right or wrong or can be used in another way to be correct??