Error testing external API function with jest (react-query: useQuery) - javascript

When using Jest to test a function I have that makes a call to external API I get an error about only being able to use a hooks inside of a functional component.
My function(useGetGophys) uses useQuery from react-query which is the hook.
I would like to be able to test the useGetGophy with jest please?
I am mocking the actual fetch request as can be seen in the test file code below.
useGetGophy.js
import { useMemo } from 'react'
import { useQuery } from 'react-query'
import urlGenerator from "../utils/urlGenerator"
export default function useGetGophys({ query, limit }) {
const url = urlGenerator({ query, limit })
const { data, status } = useQuery(["gophys", { url }], async () => {
const res = await fetch(url)
return res.json()
})
return {
status,
data,
}
}
Test file
useGetGophy.test.js
import useGetGophys from '../services/useGetGophys'
import { renderHook } from '#testing-library/react-hooks'
import { QueryClient, QueryClientProvider } from "react-query"
const desiredDataStructure = [{
id: expect.any(String),
images: {
fixed_width_downsampled: {
url: expect.any(String),
width: expect.any(String),
height: expect.any(String),
},
},
embed_url: expect.any(String),
bitly_gif_url: expect.any(String),
url: expect.any(String),
title: expect.any(String),
}]
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(desiredDataStructure)
})
)
describe('getGetGophy - ', () => {
test('returns correctly structured data', async () => {
const gophys = useGetGophys('https://api.giphy.com/v1/gifs/trending?q=daniel&api_key=00000000&limit=15&rating=g')
expect(gophys).toBe(desiredDataStructure)
})
})

You would need to render your hook using testing-library/react-hooks. As long as you are returning an object, check for result.current.data, something like:
import { renderHook } from '#testing-library/react-hooks';
test('returns correctly structured data', () => {
const { result } = renderHook(() => useGetGophys('yourUrl'));
expect(result.current.data).toEqual(desiredDataStructure);
});

Related

About getting data from RTK Query to Firebase Realtime Database

I am currently trying to get data from a Firebase Realtime Database using RTK Query. However, the code here is giving me an error because the value returned in return is not correct. If anyone has knowledge of this, I would appreciate it if you could correct the code to the correct one.
import { createApi, fakeBaseQuery } from "#reduxjs/toolkit/query/react";
import { onValue, ref } from "firebase/database";
import { db } from "libs/firebase";
export const userApi = createApi({
baseQuery: fakeBaseQuery(),
endpoints: builder => ({
getUser: builder.query({
queryFn(uid) {
try {
onValue(ref(db, `users/user${uid}`), snapshot => {
return { data: snapshot.val() };
});
} catch (e) {
return { error: e };
}
},
}),
}),
});
export const { useGetUserQuery } = userApi;
import { configureStore } from "#reduxjs/toolkit";
import { userApi } from "./apiSlice";
export const store = configureStore({
reducer: {
[userApi.reducerPath]: userApi.reducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(userApi.middleware),
});
const { data: user, error, isLoading, isSuccess } = useGetUserQuery(1);
console.log(user);
error message
Try something along the lines of
export const userApi = createApi({
baseQuery: fakeBaseQuery(),
endpoints: (builder) => ({
getUser: builder.query({
async queryFn(uid) {
try {
const data = await new Promise((resolve, reject) =>
onValue(
ref(db, `users/user${uid}`),
(snapshot) => resolve(snapshot.toJSON()),
reject
)
);
return { data };
} catch (e) {
return { error: e };
}
},
}),
}),
});

How to clear/reset mocks in Vitest

I have a simple composable useRoles which I need to test
import { computed } from "vue";
import { useStore } from "./store";
export default function useRoles() {
const store = useStore();
const isLearner = computed(() => store.state.profile.currentRole === "learner");
return {
isLearner
};
}
My approach of testing it is the following
import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";
describe("useRoles", () => {
afterEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should verify values when is:Learner", () => { // works
vi.mock("./store", () => ({
useStore: () => ({
state: {
profile: {
currentRole: "learner"
},
},
}),
}));
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", () => { //fails
vi.mock("./store", () => ({
useStore: () => ({
state: {
profile: {
currentRole: "admin"
},
},
}),
}));
const { isLearner } = useRoles(); // Values are from prev mock
expect(isLearner.value).toBeFalsy();
});
});
And useStore is just a simple function that I intended to mock
export function useStore() {
return {/**/};
}
The first test runs successfully, it has all the mock values I implemented but the problem is that it's not resetting for each test (not resetting at all). The second test has the old values from the previous mock.
I have used
vi.clearAllMocks();
vi.resetAllMocks();
but for some reason clear or reset is not happening.
How can I clear vi.mock value for each test?
Solution
As it turned out I should not be called vi.mock multiple times. That was the main mistake
Substitutes all imported modules from provided path with another module. You can use configured Vite aliases inside a path. The call to vi.mock is hoisted, so it doesn't matter where you call it. It will always be executed before all imports.
Vitest statically analyzes your files to hoist vi.mock. It means that you cannot use vi that was not imported directly from vitest package (for example, from some utility file)
Docs
My fixed solution is below.
import useRoles from "./useRoles";
import { useStore } from "./store"; // Required the mock to work
vi.mock("./store");
describe("useRoles", () => {
afterEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should verify values when is:Learner", () => {
// #ts-ignore it is a mocked instance so we can use any vitest methods
useStore.mockReturnValue({
state: {
profile: {
currentRole: "learner",
},
},
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", () => {
// You need to use either #ts-ignore or typecast it
// as following (<MockedFunction<typeof useStore>>useStore)
// since original function has different type but vitest mock transformed it
(<MockedFunction<typeof useStore>>useStore).mockReturnValue({
state: {
profile: {
currentRole: "admin",
},
},
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeFalsy();
});
});
vitest = v0.23.0
I ran into the same issue and was able to find a workaround. In your case it should look like this:
import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";
vi.mock("./store");
describe("useRoles", () => {
it("should verify values when is:Learner", async () => {
const store = await import("./store");
store.useStore = await vi.fn().mockReturnValueOnce({
useStore: () => ({
state: {
profile: {
currentRole: "learner"
},
},
}),
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", async () => {
const store = await import("./store");
store.useStore = await vi.fn().mockReturnValueOnce({
useStore: () => ({
state: {
profile: {
currentRole: "admin"
},
},
}),
});
const { isLearner } = useRoles(); // Value is from current mock
expect(isLearner.value).toBeFalsy();
});
});

Next.js GetStaticPaths: ReferenceError: Cannot access 'getAllPostIds' before initialization

I am making a simple next js blog type of application using graphql as a data fetching backend to render text. Using getStaticPaths, I'm running into the following error when I try to fetch data for my page.
ReferenceError: Cannot access 'getAllPostIds' before initialization
Here is my code:
pages/posts/[id].tsx
import { getAllPostIds } from '../../../lib/posts'
const Post = ({ postData }) => {
... code.....
}
export const getStaticPaths = async () => {
const paths = getAllPostIds('aws');
return {
paths,
fallback: false
}
}
export default Post;
And here is my posts.ts where I use graphql to fetch data.
import { useQuery } from "react-query";
import { GraphQLClient } from "graphql-request";
const GET_POST_IDS = gql`
query($folder: String!) {
repository(owner: "assembleinc", name: "documentation") {
object(expression: $folder) {
... on Tree {
entries {
name
}
}
}
}
}`
;
const graphQLClient = new GraphQLClient('https://api.github.com/graphql', {
headers: {
Authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`
}
});
export const getAllPostIds = (folder: String) => {
return useQuery(folder, async () => {
... fetch data ...
});
}
Essentially, before I can even get the data through graphql, next js is complaining that getAllPostIds can't be initialized even though I import it at the top. Is there some next.js magic that I am not seeing?

How can i test an API call in vuejs using jest?

im having this method in my component that makes an API call with axios, I checked the docs on how to test it but I cant seem to figure out how to do so. Any help would be appreciated.
loadContents() {
axios.get('/vue_api/content/' + this.slug).then(response => {
this.page_data = response.data.merchandising_page
}).catch(error => {
console.log(error)
})
},
You could use moxios or axios-mock-adapter to automatically mock Axios requests. I prefer the latter for developer ergonomics.
Consider this UserList component that uses Axios to fetch user data:
// UserList.vue
export default {
data() {
return {
users: []
};
},
methods: {
async loadUsers() {
const { data } = await axios.get("https://api/users");
this.users = data;
}
}
};
With axios-mock-adapter, the related test stubs the Axios GET requests to the API URL, returning mock data instead:
import axios from "axios";
const MockAdapter = require("axios-mock-adapter");
const mock = new MockAdapter(axios);
import { shallowMount } from "#vue/test-utils";
import UserList from "#/components/UserList";
describe("UserList", () => {
afterAll(() => mock.restore());
beforeEach(() => mock.reset());
it("loads users", async () => {
mock
.onGet("https://api/users")
.reply(200, [{ name: "foo" }, { name: "bar" }, { name: "baz" }]);
const wrapper = shallowMount(UserList);
await wrapper.vm.loadUsers();
const listItems = wrapper.findAll("li");
expect(listItems).toHaveLength(3);
});
});
demo

Mock inner axios.create()

I'm using jest and axios-mock-adapter to test axios API calls in redux async action creators.
I can't make them work when I'm using a axios instance that was created with axios.create() as such:
import axios from 'axios';
const { REACT_APP_BASE_URL } = process.env;
export const ajax = axios.create({
baseURL: REACT_APP_BASE_URL,
});
which I would consume it in my async action creator like:
import { ajax } from '../../api/Ajax'
export function reportGet(data) {
return async (dispatch, getState) => {
dispatch({ type: REQUEST_TRANSACTION_DATA })
try {
const result = await ajax.post(
END_POINT_MERCHANT_TRANSACTIONS_GET,
data,
)
dispatch({ type: RECEIVE_TRANSACTION_DATA, data: result.data })
return result.data
} catch (e) {
throw new Error(e);
}
}
}
Here is my test file:
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
let mock = new MockAdapter(axios)
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})
And I only get one action Received: [{"type": "REQUEST_TRANSACTION_DATA"}] because there was an error with the ajax.post.
I have tried many ways to mock the axios.create to no avail without really knowing what I'm doing..Any Help is appreciated.
OK I got it. Here is how I fixed it! I ended up doing without any mocking libraries for axios!
Create a mock for axios in src/__mocks__:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios
Then in your test file, the gist would look like:
import mockAxios from 'axios'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
// for some reason i need this to fix reducer keys undefined errors..
jest.mock('../../store/rootStore.ts')
// you need the 'async'!
test('Retrieve transaction data based on a date range', async () => {
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore()
const mockData = {
'data': 123
}
/**
* SETUP
* This is where you override the 'post' method of your mocked axios and return
* mocked data in an appropriate data structure-- {data: YOUR_DATA} -- which
* mirrors the actual API call, in this case, the 'reportGet'
*/
mockAxios.post.mockImplementationOnce(() =>
Promise.resolve({ data: mockData }),
)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
// work
await store.dispatch(reportGet())
// assertions / expects
expect(store.getActions()).toEqual(expectedActions)
expect(mockAxios.post).toHaveBeenCalledTimes(1)
})
If you need to create Jest test which mocks the axios with create in a specific test (and don't need the mock axios for all test cases, as mentioned in other answers) you could also use:
const axios = require("axios");
jest.mock("axios");
beforeAll(() => {
axios.create.mockReturnThis();
});
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
Here is the link to the same example of Axios mocking in Jest without create. The difference is to add axios.create.mockReturnThis()
here is my mock for axios
export default {
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
create: jest.fn(function () {
return {
interceptors:{
request : {
use: jest.fn(() => Promise.resolve({ data: {} })),
}
},
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
}
}),
};
In your mockAdapter, you're mocking the wrong instance. You should have mocked ajax instead. like this, const mock = MockAdapter(ajax)
This is because you are now not mocking the axios instance but rather the ajax because it's the one you're using to send the request, ie, you created an axios instance called ajax when you did export const ajax = axios.create...so since you're doing const result = await ajax.post in your code, its that ajax instance of axios that should be mocked, not axios in that case.
I have another solution.
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
// import axios from 'axios'
import { ajax } from '../../api/Ajax' // axios instance
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
// let mock = new MockAdapter(axios)
let mock = new MockAdapter(ajax) // this here need to mock axios instance
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})
another method: add this file to src/__mocks__ folder
import { AxiosStatic } from 'axios';
const axiosMock = jest.createMockFromModule<AxiosStatic>('axios');
axiosMock.create = jest.fn(() => axiosMock);
export default axiosMock;
The following code works!
jest.mock("axios", () => {
return {
create: jest.fn(() => axios),
post: jest.fn(() => Promise.resolve()),
};
});

Categories

Resources