I have a chat application, it has a sidebar with conversations of a user, when one of the conversations is clicked I expect messages from that conversation to be loaded and displayed on the screen (Very similar to Telegram). I recently started writing tests for this functionality and encountered a problem: First I render the components with the mocked store
beforeEach(async () => {
renderWithProviders(
<>
<Conversations conversations={mockConversationsArray} />
<Messages />
</>
);
});
The renderWithProviders comes from this file:
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
preloadedState?: PreloadedState<RootState>;
store?: AppStore;
}
const reducer = combineReducers({
authReducer,
socketReducer,
searchReducer,
chatReducer,
screenReducer,
});
export function renderWithProviders(
ui: React.ReactElement,
{
preloadedState = {},
store = configureStore({ reducer, preloadedState }),
...renderOptions
}: ExtendedRenderOptions = {}
) {
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
return <Provider store={store}>{children}</Provider>;
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}
Then I find the paragraph tag that I want to click, and click on it using the "@testing-library/user-event"
module. I also mock the response from the server using msw
. When the conversation is clicked, it is expected to dispatch the setConversation
action:
static setConversation(conversation: IConversation, socket: SocketType) {
return async (dispatch: AppDispatch) => {
try {
ConversationEmitters.joinUser(socket, conversation);
const res = await ChatServices.getMessages(conversation.id, 0);
dispatch(chatSlice.actions.setCurrentConversation(conversation));
dispatch(chatSlice.actions.updateManyMessages(res.data.messages));
dispatch(chatSlice.actions.setMessagesSentDuringSession(0));
dispatch(chatSlice.actions.setMessagesLoaded(res.data.messages.length));
} catch (err) {
dispatch(chatSlice.actions.setError("No messages sent"));
}
};
}
The function that dispatches it in the regular code (The conversation is passed via props):
const handleJoin = (conversation: IConversation) => {
if (socket) {
dispatch(ChatActions.setConversation(conversation, socket));
}
};
After clicking I try to asynchronously find the message by text, but instead of finding the message I get an error saying that it is not on the screen (Here is the full code for the test):
it("Loads messages when a conversation is clicked", async () => {
const conv = screen.getByText(/mockUsername2/i);
server.use(
rest.post(`${API_URL}/chat/messages/0`, (req, res, ctx) => {
return res(ctx.json({ messages: mockMessages }));
})
);
user.click(conv);
expect(await screen.findByText("How is your day")).ToBeInTheDocument();
});
});
Initially the request was not even being made, when I tried to explicitly make an axios request and dispatch actions in the test and log the store to the console, the messages were filled with the objects from the mocked request, but they were not displaying in the dom, and the element was still not found by findByText
const res = await ChatServices.getMessages("mockID1", 0);
store.dispatch(
chatSlice.actions.setCurrentConversation(mockConversationsArray[0])
);
store.dispatch(chatSlice.actions.updateManyMessages(res.data.messages));
The error:
Unable to find an element with the text: How is your day. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your
matcher more flexible.
Ignored nodes: comments, script, style
<body>
<div>
<div
class="modules__conversation_populated"
>
<div
class="elements__single-conversation"
>
<p>
mockUsername1
</p>
</div>
<div
class="elements__single-conversation"
>
<p>
mockUsername2
</p>
</div>
<div
class="elements__single-conversation"
>
<p>
mockUsername3
</p>
</div>
</div>
<div
class="modules__messages"
>
<div
class="modules__messages__loaderWrapper"
>
<img
alt="loading..."
class="elements__loader"
src="/images/loading.gif"
/>
</div>
</div>
</div>
</body>
I am relatively new to testing with RTL and jest, is it a correct way of dispatching redux actions and if it is, why do the messages not rerender when the state changes?