Jest mock Typeorm Datasource in unit tests (without nestjs) - postgresql

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();
});
});

Related

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."

Apollo Server Express v4 GraphQL this.findOneById is not a function

I have the following ApolloServer (v4)
import { MongoDataSource } from 'apollo-datasource-mongodb'
export default class LoaderAsset extends MongoDataSource {
async getAsset(assetId) {
return this.findOneById(assetId) // ERROR IS HERE
}
}
async function startApolloServer(app) {
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
});
await server.start();
app.use(
'/graphql',
cors(),
bodyParser.json(),
expressMiddleware(server, {
context: async ({ req }) => {
return {
dataSources: {
loaderAsset: new LoaderAsset(modelAsset),
}
}
},
}),
);
const port = config.get('Port') || 8081;
await new Promise(resolve => httpServer.listen({ port }, resolve));
}
when I run graphql and send one assetId, everything is working till I get following error:
this.findOneById is not a function
By the way (this) has collection and model objects but not any methods.
is it because apollo-datasource-mongodb is not compatible with the new version of apollo server v4?
dataSources in v3 were as follows:
dataSources: () => ({
users: new Users(client.db().collection('users'))
// OR
// users: new Users(UserModel)
})
but in the new version dataSources is inside the context
Maybe the issue is because of this change.
Credit to: https://github.com/systemkrash on https://github.com/GraphQLGuide/apollo-datasource-mongodb/issues/114
what i did is i override my datasource class so I can call the initialize method inside in the MongoDatasource class. Now it works for me.
import { MongoDataSource } from 'apollo-datasource-mongodb';
class ReceptionDataSource extends MongoDataSource {
constructor({ collection, cache }) {
super(collection);
super.initialize({ context: this.context, cache });
}
async getReception(receptionId) {
return await this.findOneById(receptionId);
}
}
export default ReceptionDataSource;
then in my context
async function startApolloServer(app) {
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
});
await server.start();
app.use(
'/graphql',
cors(),
bodyParser.json(),
expressMiddleware(server, {
context: async ({ req }) => {
const { cache } = server
return {
dataSources: {
loaderAsset: new ReceptionDataSource({collection: ReceptionModel, cache}),
}
}
},
}),
);
const port = config.get('Port') || 8081;
await new Promise(resolve => httpServer.listen({ port }, resolve));
}

mongodb client not connecting in jest test

I am trying to setup an integration test for apollo graphql and need to connect to my mongodb database but this doesnt seem to be working. See the below code:
const MongoClient = require('mongodb').MongoClient
const LOADER_URI = 'mongodb://localhost:27017'
describe('ApolloIntegrationTest', () => {
test('Test', () => {
MongoClient.connect(URI, (err, client) => {
if(err) throw err
const pulseCore = client.db('pulse-core')
console.log(pulseCore)
const books = pulseCore.collection('books')
console.log(books)
client.close()
})
})
});
When i run the test, i expected to see the console logs printing but nothing prints when the test finishes. Not sure what i am doing wrong here.
You can use beforeAll and afterAll to simplify this.
const MongoClient = require('mongodb').MongoClient;
const LOADER_URI = 'mongodb://localhost:27017'
describe('ApolloIntegrationTest', () => {
let connection;
beforeAll(async () => {
connection = await MongoClient.connect(LOADER_URI);
});
afterAll(async () => {
await connection.close();
});
test('Test', async () => {
const pulseCore = await connection.db('pulse-core');
console.log(pulseCore);
const books = pulseCore.collection('books');
console.log(books);
});
});

Redux Toolkit state doesn't update even after adding extraReducers

I have a very limited understanding of the redux toolkit compared to its previous version. I am struggling to understand why isn't my state getting updated on the trigger of getUsers.
I have added the extraReducers as well.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { fetchSample } from './filterAPI';
export const getUsers = createAsyncThunk(
'primaryFilters/getUsers',
async (dispatch, getState) => {
console.log(getState, dispatch);
const response = await fetchSample();
return response;
}
);
const primaryFiltersSlice = createSlice({
name: 'primaryFilters',
initialState: {
dateFilter: {
dates: {
key: 'selection',
startDate: new Date(),
endDate: new Date(),
},
dummyData: null,
},
status: null,
},
extraReducers: (builder) => {
builder
.addCase(getUsers.pending, (state) => {
state.status = 'loading';
})
.addCase(getUsers.fulfilled, (state, action) => {
state.status = 'idle';
state.dummyData = action.payload;
})
.addCase(getUsers.rejected, (state, action) => {
state.status = 'failed';
});
},
});
export default primaryFiltersSlice.reducer;
Here's fetchSample function:
export const fetchSample = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
return response.json();
};
Additionally, I want to point out that my status is triggering from pending to idle and so on but my actual state isn't updating.
Here's the screenshot for the same:
I would also like to know how can we log the messages within those extraReducers.
For one, looking at your data structure, you probably want to update state.dateFilter.dummyData, not state.dummyData - at least assuming you want to match your initialState structure.
Also, createAsyncThunk does not take a callback dispatch, getState:
correct would be
export const getUsers = createAsyncThunk(
'primaryFilters/getUsers',
async (arg, {dispatch, getState}) => {
(but that does not make a difference here since you are using neither)
As for logging... just console.log? Or do you mean you are getting a Proxz object? In that case console.log(current(state))
For some reason, I am able to dispatch actions by keeping the status update at the bottom instead of the top. I would love to have a better explanation for the same, here's what exactly fixed my code:
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { fetchSample } from './filterAPI';
export const getUsers = createAsyncThunk(
'primaryFilters/getUsers',
async (dispatch, getState) => {
console.log(getState, dispatch);
const response = await fetchSample();
return response;
}
);
// export const updateDates = () => {
// }
const primaryFiltersSlice = createSlice({
name: 'primaryFilters',
initialState: {
dateFilter: {
dates: {
key: 'selection',
startDate: new Date(),
endDate: new Date(),
},
dummyData: null,
},
status: null,
},
reducer: {
updateDate: (state, action) => {
console.log('Actions = ', action);
},
},
extraReducers: (builder) => {
builder
.addCase(getUsers.pending, (state) => {
state.status = 'loading';
})
.addCase(getUsers.fulfilled, (state, action) => {
state.dummyData = action.payload;
state.status = 'idle';
})
.addCase(getUsers.rejected, (state, action) => {
state.status = 'failed';
});
},
});
export default primaryFiltersSlice.reducer;

Cannot read property 'find' of inherited method in unit tests

im having an issue trying to run an e2e test for my NestJS application tha uses a mongodb-memory-server to run mongo in memory
my e2e test looks like this
describe('UsersController.e2e', () => {
let app: INestApplication;
let module: TestingModule;
const mongod = new MongoMemoryServer();
beforeAll(async () => {
const port = await mongod.getPort();
const database = await mongod.getDbName();
module = await Test.createTestingModule({
providers: [UserRepository, UserService],
controllers: [UserController],
imports: [TypeOrmModule.forRootAsync({
useFactory: () => {
return {
type: 'mongodb',
host: '127.0.0.1',
port,
database,
entities: [__dirname + '../../../**/*.entity{.ts,.js}'],
} as TypeOrmModuleOptions;
},
}),
TypeOrmModule.forFeature([User])
]
}).compile();
app = module.createNestApplication();
await app.init();
});
afterAll(async () => {
await module.close();
await app.close();
});
describe('GET /users', () => {
it('should return a collection of user resources', async () => {
const { body } = await supertest
.agent(app.getHttpServer())
.get('/users')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(body).toEqual(userCollectionMock);
});
});
});
when running the test it throws a 500 error
Cannot read property 'find' of undefined
TypeError: Cannot read property 'find' of undefined
at UserRepository.Object.<anonymous>.MongoRepository.find (src/repository/MongoRepository.ts:77:29)
at UserRepository.index (src/modules/user/repositories/user.repository.ts:12:20)
the repository class looks like
export class UserRepository extends MongoRepository<User> implements IResourceRepository<User> {
index(): Promise<User[]> {
return this.find();
}
}
the extension of MongoRepository provides find() https://github.com/typeorm/typeorm/blob/master/src/repository/MongoRepository.ts#L76
So it perplexes me as to why it is undefined!
I know this is a month old, but I just ran into this issue and resolved it.
I'm using mongoose + typegoose with mongodb-memory-server.
import { MongoMemoryServer } from 'mongodb-memory-server';
describe('Auth', () => {
let app: INestApplication;
let mongoServer: MongoMemoryServer;
beforeAll(async () => {
// Set up database
mongoServer = new MongoMemoryServer();
const mongoUri = await mongoServer.getUri();
const moduleRef = await Test.createTestingModule({
imports: [
...
TypegooseModule.forRoot(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
...
],
controllers: [...],
providers: [...],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
afterAll(async () => {
await mongoServer.stop();
});
});
The main mistake I was making was that I had separately overwritten my User Typegoose injection.
providers: [
// THIS WAS MY MISTAKE!
{
provide: getModelToken('User'),
useValue: mockUserModel,
},
],
I know this is not a complete answer to your question, but I hope this might help you.