Mocking an api call in Jest with React Typescript - axios

I'm following some documentation on mocking an api call with Jest, although trying to do it with react.tsx.
I've looked at a lot of different stack Q&As and elsewhere online and am not understanding what I am missing from my test file to make my test pass.
So far I'm exporting my fetchWeatherData function from my WeatherMocks.tsx:
import axios from 'axios';
export const fetchWeatherData = async () => {
const response = await axios.get('http://mock-api-call/weather/get-weather');
return response.data.result.weather.forcast;
};
and importing to my test file where I am trying to use this function to mock the data.
Weather.test.tsx:
import axios from 'axios';
import { fetchWeatherData } from '../../__mocks__/WeatherMocks';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('mock api calls', () => {
afterEach(() => {
jest.resetAllMocks();
});
test('return forcast Sunny', async () => {
mockedAxios.get.mockResolvedValue({
data: {
result: {
weather: {
forcast: 'Sunny',
max: 28,
min: 17,
description: 'Clear skys all day with a warm summber breaze ariving in the afternoon',
},
},
},
});
const forecast = await fetchWeatherData();
expect(forecast.forcast).toEqual('Sunny');
});
});
If someone can help me get past this hurdle I would greatly appreciate it as I was told this is a really simple method.
The new testing error
expect(received).toEqual(expected) // deep equality
Expected: "Sunny"
Received: undefined
24 | });
25 | const forecast = await fetchWeatherData();
> 26 | expect(forecast.forcast).toEqual('Sunny');
| ^
27 | });
28 | });
29 |

Second answer
It is failing because in your method you are already returning the weather:
// ...
const forecast = await fetchWeatherData();
const expect(forecast).toEqual('Sunny');
// ...
Previous answer
What you are doing is already correct, however you are not creating the object structure which you are later accessing:
import axios from 'axios';
import { fetchWeatherData } from '../../__mocks__/WeatherMocks';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('mock api calls', () => {
afterEach(() => {
jest.resetAllMocks();
});
test('return forcast Sunny', async () => {
mockedAxios.get.mockResolvedValue({
// You need to mock all the structure:
// response -> data -> result -> weather -> forcast
// You have only:
// response -> data
data: {
result: {
weather: {
// Are you sure its forcast and not forecast??
forcast: 'Sunny',
max: 28,
min: 17,
description: 'Clear skys all day with a warm summber breaze ariving in the afternoon',
},
},
},
});
const forecast = await fetchWeatherData();
expect(forecast).toEqual('Sunny');
});
});

Related

Jest mock Typeorm Datasource in unit tests (without nestjs)

I am working on creating unit tests for a project that uses Typeorm without Nestjs. The file I am creating unit tests for uses queryRunner to start a transaction. My problem is, I am not able to mock the Datasource. I tried multiple ways but the mock is never getting called to replace the actual Datasource that has access to the postgresql database. I saw some solutions to mock it, but they all use Nestjs, which I don't use in my case.
The error I am having in the tests right now is:
Received promise rejected instead of resolved
Rejected to value: [TypeORMError: Driver not Connected]
Any help would be highly appreciated since I'm not an expert in unit tests.
Here's an example of the code that I am having the issue with (replaced some names tho):
datasource.ts
//database config is defined in this file
export const datasource: DataSource = new DataSource(some_config);
dummy.service.ts
export const dummyService = () => {
//datasource is imported from the above file
const queryRunner = datasource.createQueryRunner();
await queryRunner.startTransaction();
try {
const foundObject = await queryRunner.manager.getRepository(MyObject).findOne({
where: { id: someId },
lock: { mode: 'pessimistic_write' },
});
//some more database calls
} catch (error) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
mock.datasource.ts
import { DataSource } from 'typeorm';
export const dataSourceMockFactory: () => MockType<DataSource> = jest.fn(
() => ({
createQueryRunner: jest.fn().mockImplementation(() => ({
connect: jest.fn(),
startTransaction: jest.fn(),
release: jest.fn(),
rollbackTransaction: jest.fn(),
manager: {
getRepository: jest.fn().mockImplementation(() => ({
create: jest.fn(),
findOne: jest.fn(() => {
return getMyDummyObject();
}),
})),
save: jest.fn(),
},
})),
}),
);
export type MockType<T> = {
// eslint-disable-next-line #typescript-eslint/ban-types
[P in keyof T]?: jest.Mock<{}>;
};
dummy.unit.test.ts
describe('dummy service test', () => {
let dataSourceMock: MockType<Typeorm.DataSource>;
beforeEach(async () => {
// This method did not work
jest.mock('typeorm', () => {
const actual = jest.requireActual('typeorm');
return {
...actual,
DataSource: dataSourceMockFactory(),
};
});
// The below method did not work either
jest.mock('./../../db/datasource', () => ({
datasource: dataSourceMockFactory(),
}));
});
afterEach(() => {
jest.clearAllMocks();
});
it('should test dummy service with transaction', async () => {
// getting an error here
await expect(
dummyFunction(),
).resolves.not.toThrow();
});
});

React-query: invalidateQueries doesn't work

I'm trying to invalidate queries when I create new comment.
const { data: comments } = useQuery("getComments", () => getComments({ originalKind: "NOTICE", originalSeq: id }));
const createCommentMutation = useMutation(postComment, {
onSuccess: async () => {
const queryClient = new QueryClient();
await queryClient.invalidateQueries("getComments");
},
});
The comment is created successfully, but invalidateQueries dose not working.
There is no default options...
every time i create comment, the query will invalidated
If you create a new QueryClient, it will have a new QueryCache, which is not associated with the cached data of your query. That's not how it works, and that's also not what any of the official examples / docs are showing.
What you have to do is get access to the client with useQueryClient() - another hook exported from react-query. This will give you the singleton QueryClient that you have put into the QueryClientProvider:
import { useQueryClient } from '#tanstack/react-query'
const queryClient = useQueryClient()
const createCommentMutation = useMutation(postComment, {
onSuccess: async () => {
await queryClient.invalidateQueries("getComments");
},
});

mongo-memory-server/nest process failed to exit gracefully. Likely leak on teardown

I have Mongoose In memory test helper that I'm using with a controller test:
Helper
import { MongooseModule, MongooseModuleOptions } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { connection } from 'mongoose';
let mongod: MongoMemoryServer;
export const rootMongooseTestModule = (options: MongooseModuleOptions = {}) =>
MongooseModule.forRootAsync({
useFactory: async () => {
mongod = await MongoMemoryServer.create();
const mongoUri = await mongod.getUri();
return {
uri: mongoUri,
...options,
};
},
});
export const closeInMongodConnection = async () => {
connection.dropDatabase();
connection.close();
if (mongod) await mongod.stop();
};
export const removeCollections = async () => {
const collections = connection.collections;
for (const key in collections) {
const collection = collections[key];
await collection.deleteMany({});
}
}
UserController Test
import { Test, TestingModule } from '#nestjs/testing';
import { MongooseModule } from '#nestjs/mongoose';
import {
closeInMongodConnection,
removeCollections,
rootMongooseTestModule,
} from '../../test/utils/MongooseTestModule';
import { UserSchema } from './user.schema';
import { UserController } from './user.controller';
import { UserService } from './user.service';
describe('UserController', () => {
let controller: UserController;
const mockUser = {
username: 'kyle',
email: 'kyle#example.com',
password: 'password',
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
imports: [
rootMongooseTestModule(),
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
],
providers: [UserService],
}).compile();
controller = module.get<UserController>(UserController);
});
afterAll(async () => {
await closeInMongodConnection();
});
afterEach(async () => {
await removeCollections();
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
describe('create', () => {
it('should return the saved object', async () => {
const createdUser = await controller.create(mockUser);
expect(createdUser.username).toBe(mockUser.username);
});
});
});
After the test I close the connection and remove all collections, but for some reason I'm still getting:
npm test --detectOpenHandles
tried this too:
npm test --runInBand --detectOpenHandles
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.
The test does finish and doesn't hang:
Test Suites: 15 passed, 15 total
Tests: 16 passed, 16 total
Snapshots: 0 total
Time: 5.369 s
When I add await connection.dropDatabase(); in the closeInMongodConnection function my tests take about 3 more seconds.
Is there anyway to fix this error? Is this just happening because opening and closing mongo connections is slow? I'm not sure what's going on because it seems like I'm doing everything I'm suppose too
Edit:
After un commenting await connection.dropDatabase(); I'm now getting:
thrown: "Exceeded timeout of 5000 ms for a hook.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

Is there any way to send a message automatically in converse.js

Is there any way to send a message automatically, I found a sendMessage trigger but it doesn't work, maybe I am doing wrong something
export const msgUser = (viewerChannelCredentials: any) => {
const {username, channel} = viewerChannelCredentials;
window.converse.plugins.add('msg-user', {
dependencies: [],
initialize: async function () {
const _converse = this._converse;
return _converse.api.trigger('sendMessage', {
chatbox: _converse.ChatBox | _converse.ChatRoom,
message: 'abc',
});
},
});
};
Another solution found that also doesn't work, it throws an error which Cannot read properties of undefined (reading '$msg')
export const msgUser = (viewerChannelCredentials: any) => {
const {username, channel} = viewerChannelCredentials;
window.converse.plugins.add('msg-user', {
dependencies: [],
initialize: async function () {
const _converse = this._converse;
var msg = _converse.env.$msg({
from: 'juliet#example.com/balcony',
to: 'romeo#example.net',
type: 'chat',
});
_converse.send(msg);
},
});
};
Calling api.trigger('sendMessage' only fires an event with the name "sendMessage", it doesn't actually send a message.
To send a message, you can call the sendMessage method on a ChatBox.
For example:
const chat = await api.chats.get(jid)
chat.sendMessage({ body: 'Hello world' });

How to inject $axios into Pinia store SSR

I'm trying to inject my axios instance into the store so that I'm able to login into the app but unfortunately I'm being unable to. I have the followed boot file
import { boot } from 'quasar/wrappers';
import axios from 'axios';
import type {AxiosResponse} from 'axios';
import type { StatusCodes } from 'http-status-codes';
export type WrappedResponse = { response?: AxiosResponse };
export const isError = (e: WrappedResponse, statusCode: StatusCodes) =>
e.response && e.response.status === statusCode;
export default boot(({ app, store }) => {
const api = axios.create({ baseURL: import.meta.env.VITE_APP_API_BASE_URL });
app.provide('axios', api);
store.$axios = api;
});
Then on my store I have:
import { defineStore } from 'pinia';
export const useAppStore = defineStore('app', {
state: () => ({
}),
getters: {
},
actions: {
async login() {
console.log(this.$axios);
console.log('Logging in from store');
}
},
});
Whenever login is called it prints undefined. Any idea on what I'm doing wrong?
You simply have to create a Pinia plugin:
export default boot(({ app, store }) => {
const api = axios.create({ baseURL: import.meta.env.VITE_APP_API_BASE_URL });
app.provide('axios', api);
store.use(() => ({ api })); // 👈
});