how to mock react-query useQuery in jest - jestjs

I'm trying to mock out axios that is inside an async function that is being wrapped in useQuery:
import { useQuery, QueryKey } from 'react-query'
export const fetchWithAxios = async () => {
...
...
...
const response = await someAxiosCall()
...
return data
}
export const useFetchWithQuery = () => useQuery(key, fetchWithAxios, {
refetchInterval: false,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
retry: 1,
})
and I want to use moxios
moxios.stubRequest('/some-url', {
status: 200,
response: fakeInputData,
})
useFetchWithQuery()
moxios.wait(function () {
done()
})
but I'm getting all sorts of issues with missing context, store, etc which I'm iterested in mocking out completely.

Don't mock useQuery, mock Axios!
The pattern you should follow in order to test your usages of useQuery should look something like this:
const fetchWithAxios = (axios, ...parameters) => {
const data = axios.someAxiosCall(parameters);
return data;
}
export const useFetchWithQuery = (...parameters) => {
const axios = useAxios();
return useQuery(key, fetchWithAxios(axios, ...parameters), {
// options
})
}
Where does useAxios come from? You need to write a context to pass an axios instance through the application.
This will allow your tests to look something like this in the end:
const { result, waitFor, waitForNextUpdate } = renderHook(() => useFetchWithQuery(..., {
wrapper: makeWrapper(withQueryClient, withAxios(mockedAxios)),
});
await waitFor(() => expect(result.current.isFetching).toBeFalsy());

Related

How to mock function in service called by another method in the service called by the controller

I'm trying to test my controller function which is:
import { InstalledPackages } from '../parser/parser.service';
import {
getOutdatedPackages,
InstalledPackageStatus,
} from './version-control.service';
interface OutdatedPackages {
dependencies: InstalledPackageStatus[];
devDependencies: InstalledPackageStatus[];
}
export async function getPackagesUpdatesToNotify(
packages: InstalledPackages,
type = 'package.json',
): Promise<OutdatedPackages> {
return getOutdatedPackages(packages.dependencies, type);
}
And having my service like this:
import { fetch } from "../common/http.service";
export async function getLastPackageVersion(
packageName: string
): Promise<VersionType> {
const url = `https://registry.npmjs.org/-/package/${packageName}/dist-tags`;
return await (<Promise<VersionType>>fetch(url));
}
export async function getOutdatedPackages(
installedPackages: PackagesVersion,
type: string
): Promise<InstalledPackageStatus[]> {
return Promise.all(
Object.keys(installedPackages).map(async (packageName) =>
getLastPackageVersion(packageName)
)
);
}
I've already tried both solutions:
import * as myService from './my.service';
it('my test', async () => {
const getLastPackageVersionSpy = jest.spyOn(myService, 'getLastPackageVersion').mockReturnValue(
Promise.resolve(42),
await getPackagesUpdatesToNotify(packages, type)
});
and
import { getLastPackageVersion } from './my.service';
import { getPackagesUpdatesToNotify } from './version-control.controller';
jest.mock('./myse.service', () => ({
...jest.requireActual('./myse.service'),
getLastPackageVersion: jest.fn(),
}));
it('my test', async () => {
(getLastPackageVersion as jest.Mock).mockResolvedValue(
Promise.resolve(42),
);
await getPackagesUpdatesToNotify(packages, type)
});
But the original function is always called instead of the mocked one.
How to mock the getLastPackageVersion method.
I'm trying to avoid using tools like rewire if possible.
Thank you
Move the getLastPackageVersion to different file, import it in the my.service and then mock it.
my.service:
import { fetch } from "../common/http.service";
import { getLastPackageVersion } from '../last-package-version';
export async function getOutdatedPackages(
installedPackages: PackagesVersion,
type: string
): Promise<InstalledPackageStatus[]> {
return Promise.all(
Object.keys(installedPackages).map(async (packageName) =>
getLastPackageVersion(packageName)
)
);
}
import * as lastPackageVersion from '../last-package-version';
it('my test', async () => {
const getLastPackageVersionSpy = jest.spyOn(lastPackageVersion, 'getLastPackageVersion').mockResolvedValue(42);
await getPackagesUpdatesToNotify(packages, type)
});
getOutdatedPackages is in the same file as the getLastPackageVersion so it cannot be mocked. In your case the getOutdatedPackages is still using the original getLastPackageVersion method.

How to override url for RTK query

I'm writing pact integration tests which require to perform actual call to specific mock server during running tests.
I found that I cannot find a way to change RTK query baseUrl after initialisation of api.
it('works with rtk', async () => {
// ... setup pact expectations
const reducer = {
[rtkApi.reducerPath]: rtkApi.reducer,
};
// proxy call to configureStore()
const { store } = setupStoreAndPersistor({
enableLog: true,
rootReducer: reducer,
isProduction: false,
});
// eslint-disable-next-line #typescript-eslint/no-explicit-any
const dispatch = store.dispatch as any;
dispatch(rtkApi.endpoints.GetModules.initiate();
// sleep for 1 second
await new Promise((resolve) => setTimeout(resolve, 1000));
const data = store.getState().api;
expect(data.queries['GetModules(undefined)']).toEqual({modules: []});
});
Base api
import { createApi } from '#reduxjs/toolkit/query/react';
import { graphqlRequestBaseQuery } from '#rtk-query/graphql-request-base-query';
import { GraphQLClient } from 'graphql-request';
export const client = new GraphQLClient('http://localhost:12355/graphql');
export const api = createApi({
baseQuery: graphqlRequestBaseQuery({ client }),
endpoints: () => ({}),
});
query is very basic
query GetModules {
modules {
name
}
}
I tried digging into customizing baseQuery but were not able to get it working.

How to mock prisma with jest-mock

I use prisma to interact with my database and i would like to use jest-mock to mock the findMany call. https://jestjs.io/docs/jest-object#jestmockedtitem-t-deep--false
brands.test.ts
import { PrismaService } from "#services/mysql.service";
import { mocked } from "jest-mock";
import faker from "#faker-js/faker";
import { GetBrands } from "./brand";
jest.mock("#services/mysql.service");
/**
* #group unit
*/
describe("Brand", () => {
afterAll(async () => {});
const mockedPrismaService = mocked(PrismaService, true);
it("should get a list of brands", async () => {
const mockedData = [
{
id: faker.datatype.uuid(),
name: faker.datatype.string(),
image: {
source: "some_source",
dtype: "some_dtype",
},
},
];
//#ts-ignore - because of relational data mockedData.image
mockedPrismaService.brand.findMany.mockResolvedValueOnce(mockedData);
const [response, error] = await GetBrands();
console.log(response, error);
});
});
mysql.service.ts
import mysql from "mysql2/promise";
import { Config } from "#config";
import { PrismaClient, Prisma } from "#prisma/client";
export const MySQLEscape = mysql.escape;
export const MySQLPreparedStatement = mysql.format;
export const PrismaService = new PrismaClient({});
export const PrismaHelper = Prisma;
However when i run this test i get the following error.
TypeError: Cannot read properties of undefined (reading 'brand')
Factory Mock
One option is to option use the factory approach when mocking your client.
jest.mock("#services/mysql.service", () => ({
PrismaService: {
brand: {
findMany: jest.fn(() => { })
}
},
}));
Then within your test, you can mock the findMany function to return your test data, then call the function being tested.
const mockedData = [...];
PrismaService.brand.findMany.mockResolvedValueOnce(mockedData);
const result = await GetBrands();
It's a bit cumbersome, but it works.
Note that in my example, I've implemented GetBrands as follows:
import { PrismaService } from "#services/mysql.service"
export const GetBrands = async () => {
const data = await PrismaService.brand.findMany();
return data;
}
Your example
In your example, you're using automatic mocking, and I'm not too familiar with it so I'm not sure how to get it working.
What seems to be happening to cause the error is your PrismaService is undefined when it's imported here:
import { PrismaService } from "#services/mysql.service";
And then calling the mocked function with an undefined parameter returns undefined:
const mockedPrismaService = mocked(undefined, true); // returns undefined
And finally, calling the following is what throws the error:
mockedPrismaService.brand.findMany.mockResolvedValueOnce(mockedData);
// TypeError: Cannot read properties of undefined (reading 'brand')
I would have thought something like this would be what you're after, but this throws an error:
jest.mock("#services/mysql.service", () => ({
PrismaService: mocked(PrismaService, true)
}));
// 6 |
// 7 | jest.mock("#services/mysql.service", () => ({
//> 8 | PrismaService: mocked(PrismaClient, true)
// | ^
// 9 | }));
Check out the docs
Might be worth checking out the Prismas documentation on unit testing, as they suggest a couple of pretty different approaches.

How to test function in class using jest

I wasn't able to make unit testing worked using jest
I'm trying to test a specific function that's calling or expecting result from other function but I'm not sure why it is not working. I'm pretty new to unit testing and really have no idea how could I make it work. currently this is what I've tried
export class OrganizationService {
constructor() {
this.OrganizationRepo = new OrganizationRepository()
}
async getOrganizations(req) {
if (req.permission !== 'internal' && req.isAuthInternal === false) {
throw new Error('Unauthenticated')
}
const opt = { deleted: true }
return this.OrganizationRepo.listAll(opt)
}
}
This is my OrganizationRepository that extends the MongoDbRepo
import { MongoDbRepo } from './mongodb_repository'
export class OrganizationRepository extends MongoDbRepo {
constructor(collection = 'organizations') {
super(collection)
}
}
and this is the MongoDbRepo
const mongoClient = require('../config/mongo_db_connection')
const mongoDb = require('mongodb')
export class MongoDbRepo {
constructor(collectionName) {
this.collection = mongoClient.get().collection(collectionName)
}
listAll(opt) {
return new Promise((resolve, reject) => {
this.collection.find(opt).toArray((err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
}
}
and this is the test that I've made
import { OrganizationService } from '../../../src/services/organization_service'
describe('getOrganizations', () => {
it('should return the list of organizations', () => {
// const OrgRepo = new OrganizationRepository()
const orgService = new OrganizationService()
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
// orgService.getOrganizations = jest.fn().mockReturnValue('')
const result = orgService.getOrganizations()
expect(result).toBe(OrgRepo)
})
})
I see two issues in the way you are testing:
1.
You are trying to test an asynchronous method, and on your test, you are not waiting for this method to be finished before your expect statement.
A good test structure should be:
it('should test your method', (done) => {
const orgService = new OrganizationService();
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
orgService.getOrganizations()
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
done();
});
})
Don't forget to put done as a parameter for your test!
You can find more about testing asynchronous functions on the Jest official documentation.
2.
In order to test your method properly, you want to isolate it from external dependencies. Here, the actual method OrganizationRepo.listAll() is called. You want to mock this method, for instance with a spy, so that you control its result and only test the getOrganizations method. That would look like this:
it('should test your method', (done) => {
const req = {
// Whatever structure it needs to be sure that the error in your method is not thrown
};
const orgService = new OrganizationService();
const orgRepoMock = spyOn(orgService['OrganizationRepo'], 'listAll')
.and.returnValue(Promise.resolve("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]"));
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]");
orgService.getOrganizations(req)
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
expect(orgRepoMock).toHaveBeenCalled(); // For good measure
done();
});
})
This way, we make sure that your method is isolated and its outcome cannot be altered by external methods.
For this particular method, I also recommend that you test the error throwing depending on the input of your method.
I was able to answer this, first is I mocked the repository using
jest.mock('path/to/repo')
const mockGetOne = jest.fn()
OrganizationRepository.protorype.getOne = mockGetOne
then the rest is the test

Testing and mocking fetch in async useEffect and async Redux-Saga

I'm testing a functional component, that use React-Hooks and Redux-Saga. I can pass parameters in URL for the component, because they are a login page component.
My URL i pass is 'localhost/access/parameter', and when this parameter exists, i need to call a async redux saga, and if the fetch is OK, i put the result in redux-store. When the result is on redux-store, i have a useEffect that verify the result and if is OK, i put her in a input.
I can mock the result with axios, but i'm migrating to use only fetch. i mock the fetch, but when i use
mount(component), provided by enzyme, i do not how to await the redux-saga termine the request and useEffect do your job. I put a console log inside a effect, saga and log the input props to see your value prop, but the value is always empty . I tried to use setImmediate() and process.nextTick().
Links i use to write the code: 1,2, 3
I'm using formik, so they pass some props to me.
My component
const Login = ({
setFieldError, errors, response, fetchDomain, location, values, handleChange, handleBlur, setFieldValue, history,
}) => {
useEffect(() => {
async function fetchUrlDomain() {
const { pathname } = location;
const [, , domain] = pathname.split('/');
if (typeof domain !== 'undefined') {
await fetchDomain(domain);
}
}
fetchUrlDomain();
}, [fetchDomain, location]);
useEffect(() => {
if (typeof response === 'string') {
setFieldError('domain', 'Domain not found');
inputDomain.current.focus();
} else if (Object.keys(response).length > 0) {
setFieldValue('domain', response.Domain);
setFieldError('domain', '');
}
}, [response, setFieldValue, setFieldError]);
return (
<input name="domain" id="domain" value={values.domain} onChange={handleChange} onBlur={handleBlur} type="text" />
);
}
const LoginFormik = withFormik({
mapPropsToValues: () => ({ domain: '' }),
enableReinitialize: false,
validateOnBlur: false,
validateOnChange: false,
})(Login);
const mapStateToProps = () => ({ });
const mapDispatchToProps = dispatch => ({
fetchDomain: (value) => {
dispatch(action({}, constants.RESET_RESPONSE_DOMAIN));
dispatch(action(value, constants.FETCH_DOMAIN_REQUEST));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormik);
My Saga
export function* fetchDomain(action) {
const url = yield `${mainUrl}/${action.payload}`;
try {
const response = yield fetch(url).then(res => res.json());
yield put(reduxAction(response , constants.FETCH_DOMAIN_SUCCESS));
} catch (e) {
yield put(reduxAction(e, constants.FETCH_DOMAIN_FAILURE));
}
}
My Reducer
case constants.FETCH_DOMAIN_FAILURE:
return { ...initialState, response: 'Domain not found' };
case constants.FETCH_DOMAIN_SUCCESS: {
const { payload } = action;
return {
...initialState,
id: payload.Id,
apis: payload.Apis,
response: payload,
};
}
case constants.RESET_RESPONSE_DOMAIN:
return { ...initialState };
My Test
it('input with fetch only', (done) => {
const mockSuccessResponse = {
Id: 'fafafafa',
Apis: [],
Domain: 'NAME',
};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
const wrapper = mount(
<Provider store={store}>
<LoginForm
history={{ push: jest.fn() }}
location={{ pathname: 'localhost/login/Domain' }}
/>
</Provider>,
);
process.nextTick(() => {
const input = wrapper.find('#domain');
console.log(input.props());
expect(input.props().value.toLowerCase()).toBe('name');
global.fetch.mockClear();
done();
});
});
I expect my input have value, but he don't. I tried to use jest-fetch-mock but just don't work, and i want to use native jest methods, no thirty party libraries.
I cannot say what's wrong with your current code. But want to propose different approach instead.
Currently you are testing both redux part and component's one. It contradicts with unit testing strategy when ideally you should mock everything except module under the test.
So I mean if you focus on testing component itself it'd be way easier(less mocks to create) and more readable. For that you need additionally export unwrapped component(Login in your case). Then you can test only its props-vs-render result:
it('calls fetchDomain() with domain part of location', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
shallow(<Login fetchDomain={fetchDomain} location={location} />);
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('example.com');
});
it('re-calls fetchDomain() on each change of location prop', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
const wrapper = shallow(<Login fetchDomain={fetchDomain} location={location} />);
fetchDomain.mockClear();
wrapper.setProps({ location: { pathName: 'another.org/path' } });
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('another.org');
});
And the same for other cases. See with this approach if you replace redux with direct call to fetch() or whatever, or if you refactor that data to come from parent instead of reading from redux store you will not need to rewrite tests removing mocks to redux. Sure, you will still need to test redux part but it also can be done in isolation.
PS and there is no profit to await fetchDomain(...) in useEffect since you don't use what it returns. await does not work like a pause and that code may rather confuse reader.

Resources