To render the container component connected to redux store, I'm using this function to render the component
function render(Component, props, storeData) {
const mockStore = configureStore([thunkMiddleware]);
const store = mockStore(storeData);
return mount(
<Provider store={store}>
<Component {...props} />
</Provider>
);
}
Now, I need to test the props change of the Component rendered but it looks like ReactWrapper.setProps only works for the Root component.
MyComponent is a container component that is connected to store using connect.
describe('MyComponent', () => {
it('should work at props change', () => {
const wrapper = render(MyComponent, { value: 1 }, initialStoreValue);
wrapper.find(MyComponent).setProps({ value: 2});
// then expect something.
});
});
Few things to consider:
redux-mock-store allows to pass a function as state source - redux-mock-store will call that function every time
to trigger re-rendering component with useSelector or connect() we can just dispatch() with any action type. Literally any.
We need to wrap updating store into act() or otherwise it might not work and definitely will complain with console.error.
So keeping that in mind we can enhance render:
function render(Component, props, initialStoreData) {
let currentStoreData = initialStoreData;
const mockStore = configureStore([thunkMiddleware]);
const store = mockStore(() => currentStoreData);
const wrapper = mount(
<Provider store={store}>
<Component {...props} />
</Provider>
);
const updateStore = (newStoreData) => {
currentStoreData = newStoreData;
act(() => {
store.dispatch({ type: '' }); // just to trigger re-rendering components
});
}
return { wrapper, updateStore };
}
And then in test:
describe('MyComponent', () => {
it('does something when store changes somehow', () => {
const { wrapper, updateStore } = render(MyComponent, { value: 1 }, initialStoreValue);
updateStore({ someReduxValue: 2});
// then expect something.
});
});
Related
I am working on a React Native application and am very new to testing. I am trying to mock a hook that returns a true or false boolean based on the current user state. I need to mock the return value of the authState variable, and based on that, I should check if the component is rendered or not. But the jest mock is returning the same value only
useAuth.ts
export const useAuthState = () => {
const [authState, setAuthState] = useState<AuthState>();
useEffect(() => {
return authentication.subscribe(setAuthState);
}, []);
return authState;
};
MyComponent.tsx
export const MyComponent = () => {
const authState = useAuthState();
if (!authState) {
return null;
}
return <AnotherComponent />
}
MyComponent.test.tsx
import { MyComponent } from "./MyComponent"
jest.mock('../use-auth-state', () => {
return {
useAuthState: () => false,
};
});
const TestComponent = () => <MyComponent />
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
let testRenderer: ReactTestRenderer;
act(() => {
testRenderer = create(<TestComponent />);
});
const testInstance = testRenderer.getInstance();
expect(testInstance).toBeNull()
})
})
This is working fine. But, I am not able to mock useAuthState to be true as this false test case is failing. Am I doing it right? I feel like I am messing up something.
You want to change how useAuthState is mocked between tests, right? You can set your mock up as a spy instead and change the mock implementation between tests.
It's also a little more ergonomic to use the render method from react-testing-library. The easiest way would be to give your component a test ID and query for it. Something like the below
import { MyComponent } from "./MyComponent"
import * as useAuthState from '../use-auth-state';
const authStateSpy = jest.spyOn(useAuthState, 'default');
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
// you can use .mockImplementation at any time to change the mock behavior
authStateSpy.mockImplementation(() => false);
const { queryByTestId } = render(<MyComponent />;
expect(queryByTestId('testID')).toBeNull();
})
I'm trying to write a test (with react-testing-library) for a component that makes an api call inside a useEffect and then updates redux state. The component should then update with the new state, but in my test, the component never seems to get updates to redux state.
I've tried logging at each step of the process: I found that the redux state was updating as expected during the batched actions in apiCall(). Then when "otherReduxAction" is called, it seems to be acting on a stale store. Moving that action out of the component fixed the problem. Same with the subsequent "setIsFetching" call. However, while moving those actions out of the component helped to persist state changes, the component still never gets the state updates.
This behavior only exists in testing. The app updates as expected.
component:
const CallsList = () => {
const dispatch = useDispatch();
const { filteredCalls, isFetching } = useSelector(selectState);
useEffect(() => {
async function fetchRecordings() {
dispatch(setIsFetching(true));
try {
await apiCall();
dispatch(otherReduxAction());
} catch (err) {}
dispatch(setIsFetching(false));
}
fetchRecordings();
}, [dispatch]);
return (
isFetching ? (
RENDER PROGRESS BAR
) : filteredCalls.length ? (
RENDER LIST OF CALLS
) : (
RENDER NO RESULTS MESSAGE
)
);
};
apiCall:
export const apiCall = (): Promise<string> => {
return new Promise(async (res, rej) => {
try {
const response = await axios.get<GetResponse>(
`${process.env.REACT_APP_API_BASE_URL}/recordings`,
);
batch(() => {
store.dispatch(setCalls(response.data.recordings));
... DISPATCH ADDITIONAL ACTIONS
});
res("success");
} catch (err) {
rej("error");
}
});
};
msw handler:
rest.get(`${process.env.REACT_APP_API_BASE_URL}/recordings`, (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ MOCKED RESPONSE OBJECT })
);
}),
custom react-testing-library render:
import { render as rtlRender } from "#testing-library/react";
...
export const render = (ui: any, initialStore = {}, { route = '/' } = {}, options = {}, ) => {
const store = createStore(rootReducer, initialStore);
const Providers = ({ children }: any) => (
<Provider store={store}>
<BrowserRouter>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</BrowserRouter>
</Provider>
);
window.history.pushState({}, 'Test page', route)
return rtlRender(ui, { wrapper: Providers, ...options });
};
test (this fails on awaiting the element with LIST ITEM TEXT:
describe("CallsList", () => {
it("renders list of recordings", async () => {
render(<CallsList />);
const progressBar = screen.getByRole("progressbar");
expect(progressBar).toBeInTheDocument();
await waitForElementToBeRemoved(circularProgress)
expect(await screen.findByText(LIST ITEM TEXT)).toBeInTheDocument();
});
});
I have a test that dispatch action to redux and I am getting Warning: React has detected a change in the order of Hooks called by Register
Previous render Next render
------------------------------------------------------
44. useLayoutEffect useLayoutEffect
45. useDebugValue useDebugValue
46. undefined useContext
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
My test:
it('should call dispatch with register and return error', async() => {
fetch.mockImplementationOnce(() => Promise.resolve(new Response(JSON.stringify({username: 'Username is taken'}), {status: 422})));
const wrapper = createWrapper();
wrapper.find('form').simulate('submit', { preventDefault: jest.fn() });
})
Creating wrapper:
const sagaMiddleware = createSaga();
const middleware = [...getDefaultMiddleware({thunk: false}), sagaMiddleware];
const store = configureStore({
reducer: {
authenticate,
},
middleware
});
sagaMiddleware.run(function*(){
yield registerWatcher
});
const createWrapper = () => {
return mount(
<Provider store={store}>
<Router>
<Register />
</Router>
</Provider>
)
}
Register:
const {isLoading, error} = useSelector(getRegisterRequest);
const dispatch = useDispatch();
const register = (e) => {
e.preventDefault();
dispatch(registerRequest(registerValues));
}
It works normally when submiting form from browser warning is given only in the test. Also context comes from redux internals I guess, I am not using context api in my code.
I want to test if the handleAddBookToCart function is called when I clicked on the button using Jest.spyOn.
Thank you in advance for your help.
const HomeComponent = props => {
const { addBookToCart } = props;
const handleAddBookToCart = id => {
addBookToCart(id);
};
return (
<button onClick={() => handleAddBookToCart(id)}></button>
)
}
//Container
const mapDispatchToProps = dispatch => ({
addBookToCart: idBook => dispatch(cartOperations.addBookToCart(idBook))
});
//file test
describe('Test home component', () => {
let container;
let wrapper;
let component;
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<HomeContainer />
</Provider>
);
container = wrapper.find(HomeContainer);
component = container.find(HomeComponent);
it('calls the handleAddBookToCart function when the button is clicked', () => {
const spy = jest.spyOn(???)
component.find('button').simulate('click');
expect(spy).toHaveBeenCalled();
});
Should you must use spyOn? You can just pass a jest mock fn as the addBookToCart prop
it('calls the handleAddBookToCart function when the button is clicked', () => {
const props = {
addBookToCart: jest.fn(),
...your other props
}
const component = shallow(<HomeComponent {...props} />)
component.find('button').simulate('click');
expect(props.addBookToCart).toHaveBeenCalled();
});
An easy way would be to send a mock into the property addBookToCart and spy on that when the button is clicked.
So when you create/mount/shallow your component for the unit test, try the following (mount coming from enzyme but feel free to use your existing method of testing components):
const spy = jest.fn();
const component = mount(<HomeComponent addBookToCart={spy} />);
I am new to jest. My component look like this.
class ComponentToTest extends React.Component {
searchName = () => {
return axios.get(...)
}
render() {
return(
<Form onSubmit={handleSubmit(save)}>
<SomeChildComponent searchName={ (searchTerm: string) => this.searchName(searchTerm)} />
</Form>
)
}
}
`ComponentToTest.test.js`
it('should call `handleSubmit` with `save`', () => {
const store = createStore((state) => state, { app: {} });
const onSave = jest.fn().mockReturnValue(() => {});
const props: any = { handleSubmit: onSave };
const tree = mount(
<Provider store={store}>
<ComponentToTest {...props} />
</Provider>,
);
tree.find('button').simulate('submit');
expect(onSave).toHaveBeenCalled();
});
I am getting error after running this test. is there any way to mock the call to searchName and return promise?