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

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.

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

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

Getting db.close is not a function in sample JEST

I'm trying to setup testing my node with mongo app using Jest. I've set it up and copied their sample verbatim, which works fine, except when I call the db.close(); function. It gives me a TypeError: that db.close is not a function. This is directly out of their example:
import { MongoClient } from 'mongodb';
describe('insert', () => {
let connection;
let db;
beforeAll(async () => {
connection = await MongoClient.connect(global.__MONGO_URI__, {
useNewUrlParser: true,
});
db = await connection.db(global.__MONGO_DB_NAME__);
});
afterAll(async () => {
await connection.close();
await db.close();
});
it('should insert a doc into collection', async () => {
const users = db.collection('users');
const mockUser = {_id: 'some-user-id', name: 'John'};
await users.insertOne(mockUser);
const insertedUser = await users.findOne({_id: 'some-user-id'});
expect(insertedUser).toEqual(mockUser);
});
});

hapi lab AssertionError [ERR_ASSERTION]: Plugin crumb already registered

i'm not sure why i am receiving this. I am trying to create a simple test while using #hapi/crumb. i am only registering it once in my server.js.
const Path = require("path");
const hapi = require("hapi");
const inert = require("inert");
const vision = require("vision");
const Ejs = require("ejs");
const Crumb = require("#hapi/crumb");
const Blankie = require("blankie");
const Scooter = require("#hapi/scooter");
const routes = require("./routes");
// Configure the server
const server = hapi.Server({
host: "0.0.0.0",
port: process.env.PORT || 3000,
routes: {
files: {
relativeTo: Path.join(__dirname, "..", "public")
},
state: {
parse: true,
failAction: "ignore"
},
security: {
xframe: true,
noOpen: false
},
cors: {
origin: ["banglarelief.org"],
headers: ["Authorization"], // an array of strings - 'Access-Control-Allow-Headers'
exposedHeaders: ["Accept"], // an array of exposed headers - 'Access-Control-Expose-Headers',
additionalExposedHeaders: ["Accept"], // an array of additional exposed headers
maxAge: 60,
credentials: true // boolean - 'Access-Control-Allow-Credentials'
}
}
});
const plugins = async () => {
const pluginsToRegister = [
inert,
vision,
require("hapi-mobile-views"),
{ plugin: Crumb, options: { cookieOptions: { isSecure: false } } },
Scooter,
{
plugin: Blankie,
options: {} // specify options here
}
];
await server.register(pluginsToRegister);
};
const init = async () => {
await plugins();
server.state("player", {
ttl: null,
clearInvalid: true,
isSecure: false
});
server.views({
engines: { ejs: Ejs },
path: `${__dirname}/views`,
layout: "layout"
});
await server.route(routes);
return server;
};
const start = async () => {
try {
await init();
await server.start();
} catch (err) {
console.log(err);
process.exit(1);
}
};
module.exports = { init, start };
My test file is very basic and i have tried to move around where the start should be called but it keep throwing same error.
'use strict';
const Lab = require('#hapi/lab');
const { expect } = require('#hapi/code');
const { afterEach, beforeEach, describe, it } = exports.lab = Lab.script();
const { init, start } = require('../src/server');
let server = start();
describe('GET /', () => {
//let server;
//server = start();
beforeEach(async () => {
//server = start();
});
afterEach(async () => {
//await server.stop();
});
it('responds with 200', async () => {
const res = await server.inject({
method: 'get',
url: '/'
});
expect(res.statusCode).to.equal(200);
});
});
I have been following https://hapijs.com/tutorials/testing?lang=en_US
The solution seems to work if you break up your plugins function into two parts. One part will init 3rd party plugins like #Hapi/*. The other function will init your 1st party plugins that you wrote. You will only init the 3rd party plugins in your start function.
It's critical that you include { once: true } because that will prevent your error. It will only initialize the plugin once, which will prevent your error. You cannot always specify { once: true } on 3rd party plugins. Thus, we have to handle that a different way. Since we moved all the 3rd party plugins to their own function, which is invoked on start, that should prevent 3rd party plugins from causing an issue of being reinitialized.
const hapiPlugins = async () => {
const pluginsToRegister = [
inert,
vision,
require("hapi-mobile-views"),
{ plugin: Crumb, options: { cookieOptions: { isSecure: false } } },
Scooter,
{
plugin: Blankie,
options: {} // specify options here
}
];
};
const myPlugins = async () => {
await server.register([
allOfMyPlugins...
],
{
once: true //critical so that you don't re-init your plugins
});
};
const init = async () => {
server.state("player", {
ttl: null,
clearInvalid: true,
isSecure: false
});
server.views({
engines: { ejs: Ejs },
path: `${__dirname}/views`,
layout: "layout"
});
await server.route(routes);
return server;
};
const start = async () => {
try {
await hapiPlugins();
await init();
await server.start();
} catch (err) {
console.log(err);
process.exit(1);
}
};
Then, you should be able to call init in your test's before function. Use that server object to inject.