Nestjs unit test: TypeError: this.userModel.findById(...).exec is not a function - unit-testing

using nestjs framework and with a repository class that uses mongoose to do the CRUD operations we have a simple users.repository.ts file like this:
#Injectable()
export class UserRepository {
constructor(#InjectModel(User.name) private userModel: Model<UserDocument>) {}
async create(createUserInput: CreateUserInput) {
const createdUser = new this.userModel(createUserInput);
return await createdUser.save();
}
}
async findById(_id: MongooseSchema.Types.ObjectId) {
return await this.userModel.findById(_id).exec();
}
and it works normally when the server is up.
consider this users.repository.spec file :
import { Test, TestingModule } from '#nestjs/testing';
import { getModelToken } from '#nestjs/mongoose';
import { Model } from 'mongoose';
// User is my class and UserDocument is my typescript type
// ie. export type UserDocument = User & Document; <-- Mongoose Type
import { User, UserDocument } from '../domain/user.model';
import { UserRepository } from './users.repository';
//import graphqlScalars from 'graphql-scalar-types';
describe('UsersRepository', () => {
let mockUserModel: Model<UserDocument>;
let mockRepository: UserRepository;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getModelToken(User.name),
useValue: Model, // <-- Use the Model Class from Mongoose
},
UserRepository,
//graphqlScalars,
],
}).compile();
// Make sure to use the correct Document Type for the 'module.get' func
mockUserModel = module.get<Model<UserDocument>>(getModelToken(User.name));
mockRepository = module.get<UserRepository>(UserRepository);
});
it('should be defined', () => {
expect(mockRepository).toBeDefined();
});
it('should return a user doc', async () => {
// arrange
const user = new User();
const userId = user._id;
const spy = jest
.spyOn(mockUserModel, 'findById') // <- spy
.mockResolvedValue(user as UserDocument); // <- set resolved value
// act
await mockRepository.findById(userId);
// assert
expect(spy).toBeCalled();
});
});
so my question:
for the should return a user doc test i get TypeError: metatype is not a constructor when and i guess
.mockResolvedValue(user as UserDocument);
should be fixed.
Note:graphql is used the query to the API and i have no idea that if the scalars should be provieded or not, if i uncomment the scalar, the expect(mockRepository).toBeDefined(); test would not pass any more
so any idea to fix the test would be apreciated.

to handle a chained .exec we should define it via mockReturnThis():
static findById = jest.fn().mockReturnThis();
I needed the constructor to be called via new so i preferd to define a mock class in this way:
class UserModelMock {
constructor(private data) {}
new = jest.fn().mockResolvedValue(this.data);
save = jest.fn().mockResolvedValue(this.data);
static find = jest.fn().mockResolvedValue(mockUser());
static create = jest.fn().mockResolvedValue(mockUser());
static remove = jest.fn().mockResolvedValueOnce(true);
static exists = jest.fn().mockResolvedValue(false);
static findOne = jest.fn().mockResolvedValue(mockUser());
static findByIdAndUpdate = jest.fn().mockResolvedValue(mockUser());
static findByIdAndDelete = jest.fn().mockReturnThis();
static exec = jest.fn();
static deleteOne = jest.fn().mockResolvedValue(true);
static findById = jest.fn().mockReturnThis();
}

Related

Trouble Writing to Jest Mocked Prisma Database

I have two databases that I need to interact with in my code. I have a simple function that takes an object and writes it to my PostgreSQL database using Prisma. I've tested the function with Postman, and it works perfectly, but when I try to execute it using a Jest mock (using the singleton pattern found in the Prisma unit testing guide), it returns undefined indicating that it didn't interact with the database and create the new record. Here's my code:
/prisma/clinical-schema.prisma
generator client {
provider = "prisma-client-js"
output = "./generated/clinical"
}
datasource clinicalDatabase {
provider = "postgresql"
url = "postgresql://postgres:postgres#localhost:5432/clinical-data?schema=public"
}
model pcc_webhook_update {
id Int #id #default(autoincrement())
event_type String
organization_id Int
facility_id Int
patient_id Int
resource_id String?
webhook_date DateTime #default(now()) #clinicalDatabase.Timestamptz(6)
status pcc_webhook_update_status #default(pending)
status_changed_date DateTime? #clinicalDatabase.Timestamptz(6)
error_count Int #default(0)
##unique([organization_id, facility_id, patient_id, resource_id, event_type, status])
}
enum pcc_webhook_update_status {
pending
processing
processed
error
}
/prisma/clinical-client.ts
import { PrismaClient } from './generated/clinical';
const prismaClinical = new PrismaClient();
export default prismaClinical;
/testing/prisma-clinical-mock.ts
import { PrismaClient } from '../prisma/generated/clinical';
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
import prisma from '../prisma/clinical-client';
jest.mock('../prisma/clinical-client', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}));
beforeEach(() => {
mockReset(prismaClinicalMock);
});
export const prismaClinicalMock = prisma as unknown as DeepMockProxy<PrismaClient>;
Everything up to this point follows the conventions outlined by the Prisma unit testing docs. The only modification I made was to make it database specific. Below is my function and tests. The request object in handle-pcc-webhooks.ts is a sample http request object, the body of which contains the webhook data I care about.
/functions/handle-pcc-webhooks/handler.ts
import prismaClinical from '../../../prisma/clinical-client';
import { pcc_webhook_update } from '../../../prisma/generated/clinical';
import { requestObject } from './handler.types';
export const handlePccWebhook = async (request: requestObject) => {
try {
const webhook = JSON.parse(request.body);
// if the webhook doesn't include a resource id array, set it to an array with an empty string to ensure processing and avoid violating
// the multi-column unique constraint on the table
const { resourceId: resourceIds = [''] } = webhook;
let records = [];
for (const resourceId of resourceIds) {
// update an existing record if one exists in the pending state, otherwise create a new entry
const record: pcc_webhook_update = await prismaClinical.pcc_webhook_update.upsert({
where: {
organization_id_facility_id_patient_id_resource_id_event_type_status: {
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
event_type: webhook.eventType,
status: 'pending'
}
},
update: {
webhook_date: new Date()
},
create: {
event_type: webhook.eventType,
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
status: 'pending' // not needed
}
});
records.push(record);
}
return records;
} catch (error) {
console.error(error);
}
};
/functions/handle-pcc-webhooks/handler.spec.ts
import fs from 'fs';
import path from 'path';
import MockDate from 'mockdate';
import { prismaClinicalMock } from '../../../testing/prisma-clinical-mock';
import { createAllergyAddRecord } from './__mocks__/allergy';
import { requestObject } from './handler.types';
import { handlePccWebhook } from './handler';
describe('allergy.add', () => {
let requestObject: requestObject;
let allergyAddRecord: any;
beforeAll(() => {
requestObject = getRequestObject('allergy.add');
});
beforeEach(() => {
MockDate.set(new Date('1/1/2022'));
allergyAddRecord = createAllergyAddRecord(new Date());
});
afterEach(() => {
MockDate.reset();
});
test('should create an allergy.add database entry', async() => {
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
// this is where I would expect handlePccWebhook to return the newly created database
// record, but instead it returns undefined. If I run the function outside of this
// unit test, with the same input value, it functions perfectly
await expect(handlePccWebhook(requestObject)).resolves.toEqual([allergyAddRecord]);
});
});
// This just builds a request object with the current webhook being tested
function getRequestObject(webhookType: string) {
// read the contents of request object file as a buffer, then convert it to JSON
const rawRequestObject = fs.readFileSync(path.resolve(__dirname, '../../sample-data/handle-pcc-webhook-request.json'));
const requestObject: requestObject = JSON.parse(rawRequestObject.toString());
// read the contents of the webhook file as a buffer, then convert it to a string
const rawWebhook = fs.readFileSync(path.resolve(__dirname, `../../sample-data/${webhookType}.json`));
const webhookString = rawWebhook.toString();
// set the body of the request object to the contents of the target webhook
requestObject.body = webhookString;
return requestObject;
}
Finally, here is the result of running the unit test:
So after banging my had against the wall for a few hours, I figured out the issue. In my handler.spec.ts file, I had the following line:
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
what that does is mock the value returned for any create functions run using Prisma. The issue is that my function is using an upsert function, which I wasn't explicitly mocking, thus returning undefined. I changed the above line to
prismaClinicalMock.pcc_webhook_update.upsert.mockResolvedValue(allergyAddRecord);
and it started working.

mock socket.io-client with jest when socket used in an es6 class

I'm writing unit tests with jest and I want to mock the socket.io-client module so I can test an es6 class that's using it.
Here's a simplified version of the class:
import io from "socket.io-client"
export default class BotClient {
io: SocketIOClientStatic
client: SocketIOClient.Socket | undefined
connected: boolean = false
attemptReconnection: boolean = true
constructor() {
this.io = io
}
init() {
try {
this.reconnectToApi()
} catch (error) {
console.error(error)
}
}
reconnectToApi(): void {
const interval = setInterval(() => {
if (this.client?.connected || !this.attemptReconnection) {
this.connected = true
this.addHooks()
clearInterval(interval)
return
}
this.client = this.io.connect("myurl")
}, 5000)
}
And I wrote my test to mock the socket.io-client module and return a custom implementation object that returns a mock function for the connect method like this:
import BotClient from "../src/lib/BotClient"
import io from "socket.io-client"
const mockClient = {
connect: jest.fn(() => {
connected: true
}),
}
jest.mock("socket.io-client", () => {
return mockClient
})
test("client will attempt connection until told to stop", () => {
jest.useFakeTimers()
const reconnectSpy = jest.spyOn(client, "reconnectToApi")
const hookSpy = jest.spyOn(client, "addHooks")
client.init("my-bot-id")
const callCount = 3
jest.advanceTimersByTime(5000 * callCount)
expect(reconnectSpy).toHaveBeenCalledTimes(1)
expect(hookSpy).toHaveBeenCalledTimes(1)
})
But when I try to run the test I get the error:
TypeError: Cannot read property 'connect' of undefined
I'm looking over the jest docs on mocking modules and using custom implementations and it seems like I'm doing it right, but obviously I'm not :/ I feel like I'm tripping over information in the docs.
How would I do this correctly? What part am I misunderstanding?

How to mock a static method in Flutter with Mockito?

I have a file a function fetchPosts() which is in charge of getting new Posts from a server and store them in a local sqlite database.
As recommended on the sqflite doc, I store a single ref to my database.
Here is the content of my database.dart file:
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DBProvider {
DBProvider._();
static final DBProvider db = DBProvider._();
static Database _database;
static Future<Database> get database async {
if (_database != null) return _database;
// if _database is null, we instantiate it
_database = await _initDB();
return _database;
}
static Future<Database> _initDB() async {
final dbPath = await getDatabasesPath();
String path = join(dbPath, 'demo.db');
return await openDatabase(path, version: 1, onCreate: _onCreate);
}
static Future<String> insert(String table, Map<String, dynamic> values) async { /* insert the record*/ }
// Other functions like update, delete etc.
}
Then I use it as such in my fetchPosts.dart file
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../services/database.dart';
const url = 'https://myapp.herokuapp.com';
Future<void> fetchPosts() {
final client = http.Client();
return fetchPostsUsingClient(client);
}
Future<void> fetchPostsUsingClient(http.Client client) async {
final res = await client.get(url);
final posts await Post.fromJson(json.decode(response.body));
for (var i = 0; i < posts.length; i++) {
await DBProvider.insert('posts', posts[i]);
}
}
In my test, how can I verify that DBProvider.insert() has been called?
fetchPosts_test.dart
import 'package:test/test.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/mockito.dart';
import 'package:../services/fetchPosts.dart';
// Create a MockClient using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockClient extends Mock implements http.Client {}
void main() {
group('fetchPosts', () {
test('update local db', () async {
final client = MockClient();
// Use Mockito to return a successful response when it calls the provided http.Client.
when(client.get()).thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
await fetchPostsWithClient(client);
verify(/* DBProvider.insert has been called ?*/);
});
});
}
The question was some while ago, but here is another solution. You can refactor calls to that static function to be called from a class "wrapper" method. This is a pattern I often use to mock requests to third party services.
Let me give you an example. To make it simple lets say Engine has 3 static methods that need to be mocked: brake() and accelerate() and speed().
class Car {
int currentSpeed;
void accelerateTo(int speed) {
while(currentSpeed > speed) {
Engine.brake();
currentSpeed = Engine.speed();
}
while(currentSpeed < speed) {
Engine.accelerate();
currentSpeed = Engine.speed();
}
}
}
Now you want to mock all calls to the engine, to do so we could refactor the code to:
class Car {
int currentSpeed;
void accelerateTo(int speed) {
while(currentSpeed > speed) {
brake();
currentSpeed = speed();
}
while(currentSpeed < speed) {
accelerate();
currentSpeed = speed();
}
}
/// wrapper to mock Engine calls during test
void brake() {
Engine.brake();
}
/// wrapper to mock Engine calls during test
int speed() {
Engine.speed();
}
/// wrapper to mock Engine calls during test
void accelerate() {
Engine.accelerate();
}
}
In the integration test you can now mock the 3 methods that interact with the static methods directly but you can now test your main method. While you could here also refactor the Engine class itself, often that class would be within a third party service.
This example is not based on the Volkswagen scandal ;).
Eventually, I had to rewrite my database.dart to make it testable / mockable.
Here's the new file:
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DBProvider {
static final DBProvider _singleton = DBProvider._internal();
factory DBProvider() {
return _singleton;
}
DBProvider._internal();
static Database _db;
static Future<Database> _getDatabase() async {
if (_db != null) return _db;
// if _database is null, we instantiate it
_db = await _initDB();
return _db;
}
static Future<Database> _initDB() async {
final dbPath = await getDatabasesPath();
String path = join(dbPath, 'demo.db');
return openDatabase(path, version: 1, onCreate: _onCreate);
}
Future<String> insert(String table, Map<String, dynamic> values) async {
final db = await _getDatabase();
return db.insert(table, values);
}
// ...
}
Now I can use the same trick as with the http.Client.
Thank you #RĂ©miRousselet
Let's say we want to test [TargetClass.someMethodCallOtherStaticMethod]
Class StaticMethodClass {
static int someStaticMethod() {};
}
Class TargetClass {
int someMethodCallOtherStaticMethod() {
return StaticMethodClass.someStaticMethod();
}
}
We should refactor [[TargetClass.someMethodCallOtherStaticMethod]] for testing,
like this:
Class TargetClass {
int someMethodCallOtherStaticMethod({#visibleForTesting dynamic staticMethodClassForTesting}) {
if (staticMethodClassForTesting != null) {
return staticMethodClassForTesting.someStaticMethod();
} else {
return StaticMethodClass.someStaticMethod();
}
}
}
Now you can write your test case like this:
// MockClass need to implement nothing, just extends Mock
MockClass extends Mock {}
test('someMethodCallOtherStaticMethod', () {
// We MUST define `mocked` as a dynamic type, so that no errors will be reported during compilation
dynamic mocked = MockClass();
TargetClass target = TargetClass();
when(mocked.someStaticMethod()).thenAnswer((realInvocation) => 42);
expect(target.someMethodCallOtherStaticMethod(staticMethodClassForTesting: mocked), 42);
})

How to mock a call to logger.warn?

I'm practicing test-first development and I want to ensure that method in a class always calls my logger at the warn level with a message. My class is defined like so:
import { log4js } from '../config/log4js-config'
export const logger = log4js.getLogger('myClass')
class MyClass {
sum(numbers) {
const reducer = (accumulator, currentValue) => accumulator + currentValue
const retval = numbers.reduce(reducer))
if (retval < 0) {
logger.warn('The sum is less than zero!')
}
return retval
}
}
const myClass = new MyClass()
export { myClass }
My test looks like this:
import { myClass, logger } from './MyClass'
import { log4js } from '../config/log4js-config'
jest.mock('log4js')
describe('MyClass', () => {
it('logs a warn-level message if sum is negative', () => {
logger.warn = jest.fn()
logger._log = jest.fn()
myClass.sum([0, -1])
expect(logger.warn).toHaveBeenCalled() // <--- fails
expect(logger._log).toHaveBeenCalled() // <--- fails
})
})
I've also tried to mock log4js.Logger._log in the setup but that didn't seem to work either. đŸ˜• Any suggestions are appreciated!
The thing with mocking is that you need to provide the mock, simplest method for me is through the mock factory. However i would recomend also some refactoring:
import { getLogger } from 'log4js'
export const logger = getLogger('myClass')
logger.level = 'debug'
// export the class itself to avoid memory leaks
export class MyClass {
// would consider even export just the sum function
sum(numbers) {
const reducer = (accumulator, currentValue) => accumulator + currentValue
const retval = numbers.reduce(reducer))
if (retval < 0) {
logger.warn('The sum is less than zero!')
}
return retval
}
}
import log4js from 'log4js';
import { MyClass } from "./class";
jest.mock('log4js', () => {
// using the mock factory we mimic the library.
// this mock function is outside the mockImplementation
// because we want to check the same mock in every test,
// not create a new one mock every log4js.getLogger()
const warn = jest.fn()
return {
getLogger: jest.fn().mockImplementation(() => ({
level: jest.fn(),
warn,
})),
}
})
beforeEach(() => {
// reset modules to avoid leaky scenarios
jest.resetModules()
})
// this is just some good habits, if we rename the module
describe(MyClass, () => {
it('logs a warn-level message if sum is negative', () => {
const myClass = new MyClass()
myClass.sum([0, -1])
// now we can check the mocks
expect(log4js.getLogger).toHaveBeenCalledTimes(1) // <--- passes
// check exactly the number of calls to be extra sure
expect(log4js.getLogger().warn).toHaveBeenCalledTimes(1) // <--- passes
})
})
Maybe simply spying on logger methods can do the trick
import { myClass, logger } from './MyClass'
describe('MyClass', () => {
it('logs a warn-level message if sum is negative', () => {
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
const _logSpy = jest.spyOn(logger, '_log').mockImplementation(() => {});
myClass.sum([0, -1])
expect(warnSpy).toHaveBeenCalled()
expect(_logSpy).toHaveBeenCalled()
})
})

Unable to add objects into Redux state during unit test

I'm attempting to add objects into my Redux store during a unit test to ensure my reducers and actions are working properly. It looks like the state that is showing up is the INITIAL_STATE from the reducer file. However, any time I add a new item into the reducer state, nothing happens. I'm presuming it's because it's adding too slow and it's async. Not really sure how to approach this.
Reducer/Action Test
/***********************
TESTING THE REDUX STORE
***********************/
describe('>>>Redux Store for Budget Functionality', () => {
beforeEach(() => {
store = configureStore();
})
it('Should successfully add a new budget into the store', () => {
let budgetCategory = testHelper.testBudgetCategory;
//Will auto dispatch since we are using redux-actions
const action = addBudgetCategoryRequest(budgetCategory);
const actual = store.getState().getIn(['budget', 'budgetCategories']).toJS()
const expected = [testHelper.testBudgetCategory];
expect(actual).toEqual(expected);
})
Store File
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
export function configureStore(){
// Use redux dev tools if available, otherwise use default composition;
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(
applyMiddleware(thunk)
));
return store;
}
My entire reducer/action create file
import Immutable from 'immutable'
import { createAction } from 'redux-actions';
import axios from 'axios';
import moment from 'moment';
/**************
INITIAL STATE
***************/
export const INITIAL_STATE = Immutable.fromJS({
budgetCategories: [],
budgetFormEditable: {errors: [{budgetNameError: false, monthlyCostError: false}] },
});
/**************
TYPES
***************/
export const ADD_BUDGET = 'src/Budget/ADD_BUDGET';
export const ADD_EDITABLE_FIELD_ERRORS = 'src/Budget/ADD_EDITABLE_FIELD_ERRORS';
export const UPDATE_BUDGET_ENTRY = 'src/Budget/UPDATE_BUDGET_ENTRY';
/**************
REDUCER LOGIC FLOW
***************/
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_BUDGET:
return state.updateIn(['budgetCategories'], arr => arr.push(Immutable.fromJS(action.payload)))
case ADD_EDITABLE_FIELD_ERRORS:
return state.setIn(['budgetFormEditable', 'errors', 0], Immutable.fromJS(action.payload))
case UPDATE_BUDGET_ENTRY:
console.log("The field that we are editing is " + action.payload.editedStateIndex);
return state.setIn(
[
'budgetCategories',
action.payload.editedStateIndex,
], Immutable.fromJS(action.payload.newBudget));
default:
return state;
}
}
/**************
ACTIONS CREATORS
***************/
export const addBudget = createAction(ADD_BUDGET);
export const addEditableFieldErrors = createAction(ADD_EDITABLE_FIELD_ERRORS);
export const updateBudgetEntry = createAction(UPDATE_BUDGET_ENTRY);
/**************
ACTION REQUESTS
***************/
//TODO: Honestly, this is pretty unnecessary as I am not resolving promises
export function addBudgetCategoryRequest(data) {
return (dispatch) => {
dispatch(addBudget(data));
}
}