I am writing feature specs for my application. The application is a music player app, and the user can edit their playlists. The feature in question I am testing edits a track, or all the tracks, in the playlist based on the argument passed in…
const [playlist, setPlaylist] = useState<Track[]>(Array(10).fill(undefined));
const [editMode, setEditMode] = useState<[boolean, number | null]>([false, null]);
const { trackNumToEdit } = useLocalSearchParams();
...
const useStoredPlaylist = async (storedPlaylist: Track[]) => {
// store the tracks from storage to playlist item
const newPlaylist = Array(10).fill(undefined)
storedPlaylist.map((track) => {
newPlaylist[track.questionNo] = track
})
setPlaylist(newPlaylist);
}
...
// If the user has come from clicking to "edit" a
// song from their album, load in their playlist
// and enter "edit" mode
useFocusEffect(
useCallback(() => {
if (trackNumToEdit) {
retrievePlaylistAnswers()
.then((res) => {
setEditMode([true, null]);
if (trackNumToEdit != "all") {
// Must cast to number as converting from route param
const trackNum = Number(trackNumToEdit);
const editingTrack = res[trackNum];
setEditMode([true, editingTrack]);
setFocusedQuestion(editingTrack.questionNo);
}
useStoredPlaylist(res);
})
.catch((error) => {
console.log("Failed to load track data for editing", error);
return Promise.reject(error);
})
}
}, [useStoredPlaylist])
);
...
return (
...
)
)
…Here are the corresponding specs for this feature…
...
const mockSpecificTrackParams = { trackNumToEdit: "0" };
const mockAllTrackParams = { trackNumToEdit: "all" };
...
describe("Editing a playlist", () => {
describe("Editing a specifc track", () => {
beforeEach(() => {
(useLocalSearchParams as jest.Mock).mockReturnValue(mockSpecificTrackParams);
});
it("loads in the stored playlist and sets the page to edit the specified track", async () => {
const firstMockTrack: Track = {
id: "1",
name: "ABC",
uri: "XYZ",
imageUrl: "WEB",
artists: "John",
questionNo: 0,
preview: "URL"
}
const secondMockTrack: Track = {
id: "2",
name: "DEF",
uri: "XYZ",
imageUrl: "WEB",
artists: "Joe",
questionNo: 1,
preview: "URL"
}
const mockResponse = [
firstMockTrack,
secondMockTrack
];
const mockRetrievePlaylistAnswers = retrievePlaylistAnswers as jest.Mock;
mockRetrievePlaylistAnswers.mockImplementation(() => Promise.resolve(mockResponse));
render(<SetPlaylist />);
await waitFor(() => {
// Expect screen to be configured to editing a specific track
});
})
})
describe("Editing all tracks", () => {
beforeEach(() => {
(useLocalSearchParams as jest.Mock).mockReturnValue(mockAllTrackParams);
});
it("loads in the stored playlist and sets the page to edit from question 1", async () => {
const firstMockTrack: Track = {
id: "1",
name: "ABC",
uri: "XYZ",
imageUrl: "WEB",
artists: "John",
questionNo: 0,
preview: "URL"
}
const secondMockTrack: Track = {
id: "2",
name: "DEF",
uri: "XYZ",
imageUrl: "WEB",
artists: "Joe",
questionNo: 1,
preview: "URL"
}
const mockResponse = [
firstMockTrack,
secondMockTrack
];
const mockRetrievePlaylistAnswers = retrievePlaylistAnswers as jest.Mock;
mockRetrievePlaylistAnswers.mockImplementation(() => Promise.resolve(mockResponse));
render(<SetPlaylist />);
await waitFor(() => {
// Expect screen to be configured to editing a specific track
});
})
})
})
The problem is that the screen matches what it should look like when the user has no stored playlist, and is not editing. I debugged this and found that, despite mocking the stored playlist answers, that the playlist’s state does not change and useStoredPlaylist()
is not called. When I debug for the case “loads in the stored playlist and sets the page to edit the specified track”, what happens is the body of “then(res)” is skipped, so the state of playlist remains unchanged. No breakpoints in useStoredPlaylist
are hit either…
What is also strange is that the spec keeps “running” after the “await” test is done, and it seems that in these debug runs, playlist
is changed and useStoredPlaylist
is hit…
I am baffled by what is happening. Why is some of the “rendered” code being run after the spec body is complete? Why is the “then(res)” statement not being entered on the first instance? I am unsure if this is a weird React Native problem or a Jest problem – hence the generalised title!
I’d appreciate any help with this or even some insight! Thank you so much.