nestjs mock i18n and request in jest service - jestjs

I have a controller like this
async create(#Body() dto: myDTO,#Req() request, #I18n() i18n?: I18nContext): Promise<MyEntity>
{
return this.Myservice.create(dto, request, i18n);
}
and service like this:
async create(dto: CreateApplicationDTO, request?, i18n?): Promise<Application> {
const MyEntity= this.repository.create(dto);
request.message = i18n.t('common.added_successful');
return this.repository.save(MyEntity);
}
and service.spec file this :
it('check number of created applications', async () => {
await service.create(myDto);
expect(await service.findAll()).toHaveLength(1);
});
When I start test show me this error
TypeError: Cannot set properties of undefined (setting 'message')
41 | return this.repository.save(newApplication);
42 | } catch (e) {
> 43 | return request.message = e.message;
| ^
44 | }
45 |
46 | }
I know I should send request and i18n to service but I don't know how mock them and send them to service in my spec file

You've set request to be optional but set a property on it regardless if it is populated or not. You need to make sure that request has a value before you try to assign to it. For simplicity sake, because you don't give it a type, it could just be {} and then {}.message = will be valid.
As for i18n in your create method you use the object method t so you need the mock to have a t method. Something like { t: jest.fn(() => 'some value') } should be enough to get the test going with a mock.
Having stronger typings will help you though, even if it means it is harder to make mocks. You could use a tool like #golevelup/ts-jest can be used to create full mocks off of types alone

Related

JEST Matcher error: received value must be a mock function

I am receiving "Matcher error: received value must be a mock function" trying to compare the result of the completed function. I do have 1 mock statement that mocks 2 methods from the utilities module for the index module to complete successfully when referencing those. I assume, though, that I do not need to mock index modules in order to pass the test. Here's the whole test that's not working:
import * as index from '../src/index';
jest.mock('../src/utils', () => {
const originalModule = jest.requireActual('../src/utils');
return {
__esModule: true,
...originalModule,
downloadModule: jest.fn(() => 'downloaded'),
compareChecksum: jest.fn(() => true)
}
});
describe('testing ingex file', () => {
test('testing case with mocked functions', async () => {
await expect(index.execute()).resolves.toHaveReturnedWith(undefined);
});
});
Utils is just a file with series of useful methods used in index. While the function called in index looks like this (Redacted):
//src/index
export async function execute() {
switch(type) { // comes from env vars
case 'file': {
await downloadModule(params); // mocked to succeed
if (await compareChecksum(otherParams)) {// mocked to succeed
console.log("Equal");
return;
}
...
}
...
}
}
The complete error I am getting:
expect(received).resolves.toHaveReturnedWith(expected)
Matcher error: received value must be a mock function
Received has value: undefined
So index module is dependent on utils and I mocked all necessary methods for it to pass successfully. Received value just cannot be a mock function ever, and it does receive the "undefined" result as expected but refuses to compare it properly. Not sure why the expected result is supposed to be a mock function in this case.
In case it matters this is typescript (not clearly visible from the code provided) and ts-jest is installed.

Typeorm Unit Test FindOneOrFail

I am trying to add unit tests for my Nest app that uses typeorm. My user service has a mock repository set up like this:
const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
Promise.resolve({
id: id,
firstName: 'John',
lastName: 'Smith',
created: '2022-08-18T04:43:26.035Z',
}),
)}
This works fine as a simple test to see if my service can return a user based on an ID passed in to it. The problem is that I use the findOneOrFail method in other endpoints like save like this:
async save(createUserDto: CreateUserDto): Promise<User> {
const newUser = this.usersRepository.create(createUserDto);
const insertedRecord = await this.usersRepository.insert(newUser);
return this.findOneOrFail(insertedRecord.identifiers[0].id);
}
Since the save returns the findOneOrFail method, if I want to test this save method it will always return the value that I set in the mock repository above so I can't test if it will return a new save value or if it returns a new updated value. Am I supposed to make the mock findOneOrFail more generic to handle if it's used in multiple endpoints?
You are creating the mock in your code here:
const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
Promise.resolve({
id: id,
firstName: 'John',
lastName: 'Smith',
created: '2022-08-18T04:43:26.035Z',
}),
)}
The Promise.resolve is what is returning the data for you. I do this a little different, and use the #golevelup/ts-jest library for easy mocking. I highly recommend this for this type of testing.
import { createMock } from '#golevelup/ts-jest'; // Using this to very easily mock the Repository
The beforeEach to create your mock
beforeEach(async () => {
...
const mockUsersRepository = createMock<usersRepository>(); // Create the full mock of the TypeORM Repository
...
In the actual test then, the simple negative test for an exception being thrown:
it('should error out if ...', async () => {
jest.spyOn(mockUsersRepository, 'create').mockImplementation( () => {
throw new Error('Mock error'); // Just error out, contents are not important
});
expect.assertions(3); // Ensure there are 3 expect assertions to ensure the try/catch does not allow code to to bypass and be OK without an exception raised
try {
await service.save(...);
} catch (Exception) {
expect(Exception).toBeDefined();
expect(Exception instanceof HttpException).toBe(true);
expect(Exception.message).toBe('Mock error');
}
});
});
In another test, that requires two steps, you can use the mockResolvedValueOnce() multiple times. For instance, maybe the call to the actual database function needs to read twice, once that it does not exist, and then once that it does. This next test covers that the code being called will do 2 findAll calls.
mockResolvedValueOnce('A').mockResolvedValueOnce('B').mockResolvedValueOnce('C') will return the 'A' for the first call, 'B' for the second, and 'C' for the 3rd and any additional calls. You can also chain mockRejectValueOnce() and others.
In your save function, what I have found is you should simply test the save() function, and have the usersRepository mocked. This way you only need to test the return from this function, without all the work against a data source. Therefore, the mock is on the save() and you simply return the instance of the User that you want.
async save(createUserDto: CreateUserDto): Promise<User> {
const newUser = this.usersRepository.create(createUserDto);
const insertedRecord = await this.usersRepository.insert(newUser);
return this.findOneOrFail(insertedRecord.identifiers[0].id);
}
However, if you want to mock just the storage calls, then the usersRepository is what you need to mock. Then you need to create the mockResolvedValueOnce (or mockResolvedValue() for all calls), against the .create() method, something against the insert() method, and then something against the findOneOrFail() method. All of these are called, so you mock needs to handle all 3.
In these examples, the mockResolvedValueOnce() is simply declaring the return, without having to do the implementation you have done via jest.fn, and mockImplementation.

How to obtain Typescript type representing any of a group of API methods?

I think my non-compiling code will explain what I'm hoping to achieve:
const fileWaiter = (method: keyof fs.promises) => async (filename: string) => {
do {
try {
return await fs.promises[method](filename);
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
} while (true)
}
export const waitFileContents = fileWaiter(fs.promises.readFile);
export const waitFileStat = fileWaiter(fs.promises.stat);
I think TS transforms might be something that could achieve this, but I'm really trying to do this kind of light metaprogramming to make code general and less repetitive, I'm not looking for tools with which to grossly over-engineer anything. I was hoping that there is some easy way to do this, but it seems that the complexity of the node.js types here inherently make it impossible to map to something like what I'm trying to do, e.g. my intellisense is telling me that typeof fs.promises.readFile is
(path: string | Buffer | URL | fs.promises.FileHandle, options?: {
encoding?: null;
flag?: fs.OpenMode;
}): Promise<Buffer>;
(path: string | Buffer | URL | fs.promises.FileHandle, options: {
...;
} | ... 9 more ... | "hex"): Promise<...>;
(path: string | ... 2 more ... | fs.promises.FileHandle, options?: "ascii" | ... 9 more ... | (fs.BaseEncodingOptions & {
...;
})): Promise<...>
And for stat this is:
(path: fs.PathLike) => Promise<fs.Stats>
I can accept that these were manually built, meticulously maintained types, and I should be able to set the type of method to the union of these two types, but it seems restricting and I wanted a quick way to indicate plainly that I want TS to use e.g. the union of all of those methods.
So, since it seems possible to accept this either as a limitation or as a quirk of fate, then I set out to write it the dumb way:
const fileWaiter = (method: (typeof fs.promises.readFile | typeof fs.promises.stat)) => async (filename: string) => {
do {
try {
return await method(filename);
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
} while (true)
}
export const waitFileContents = fileWaiter(fs.promises.readFile);
export const waitFileStat = fileWaiter(fs.promises.stat);
Now this compiles and I'm still working on coming up with a way to generalize listing out the methods I want to be able to use with it in a cleaner way.

How to verify results of spied function (callThrough)

I want to verify/assert the results of spied function. I'm using nestjs framework with jasmine. I create a jasmine spy on a method i want to "spy" on, that is, eavesdrop args and response/exception. However, I can't access return value of spied method.
Let's say I have an emitter and listener and I want to assert that my listener throws an exception when a DB operation fails.
Listener:
onModuleInit() {
this.emitter.on('documentDeleted', d => this.onDocumentDeleted(d));
}
#CatchAndLogAnyException()
private async onDocumentDeleted(dto: DocumentDeletedEventDTO) {
this.logger.log(`Deleting document with id '${dto.id}'...`);
const result = await this.ResearchHearingTestModel.deleteOne({ _id: dto.id });
if (!result.ok) {
throw new DataAccessException(
`Deleting document with id '${dto.id}' failed. Model.deleteOne(id) result: ${result}`,
);
}
if (result.n < 1) {
throw new DocumentNotFoundException(`Deleting document with id '${dto.id}' failed.`);
}
this.logger.log(`Deleted document with id '${dto.id}.`);
}
Test:
const mockId = 123;
const spyDelete = spyOn(model, 'deleteOne').and.returnValue({ ok: 1, n: 0 });
const spyOnDeleted = spyOn(listener, 'onDocumentDeleted');
spyOnDeleted.and.callThrough();
await emitter.emit('documentDeleted', new DocumentDeletedEventDTO(mockId));
expect(spyOnDeleted).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
expect(spyOnDeleted).toThrow(DocumentNotFoundException);
So when debugging, I can see spyOnDeleted["[[Scopes]]"][0].spy.calls.mostRecent["[[Scopes]]"][0].calls[0].returnValue is a promise i'm probably looking for, but I can't access it or verify on it.
And when I run the test, this is the output:
expect(received).toThrow(expected)
Expected name: "DocumentNotFoundException"
Received function did not throw
95 | expect(spyDelete).toHaveBeenCalledTimes(1);
96 | expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
> 97 | expect(spyOnDeleted).toThrow(DocumentNotFoundException);
| ^
98 | });
99 | });
100 | });
I've seen CallThrough injected spy and several other questions that are similar, but I'm still hoping it's possible to spy on callThrough methods and eavesdrop on in/out of it. Any suggestions?
toThrow cannot be used on spies. You can use spies to mock behavior or use the actual behavior with callThrough and then make sure the method was called with specific parameters. But a spy will not have information about the result it produced (value or error) so you cannot set expectations on it.
If you want to test the behavior of onDocumentDeleted you have to either test it indirectly by observing the effects of the method. In your case (with #CatchAndLogAnyException), it seems to write to the log!? So you can spy on the log and expect it to be called with the error message. Or alternatively, you test the method directly by making it public.

Jest with fetch-mock generating error: TypeError: Cannot read property 'prototype' of undefined when using on nodejs

I'm almost sure it's my mistake, but I spent one entire day trying to solve and I've being failed 😞.
I'm trying to configure the fetch-mock on node to use with jest. I tried a lot of things and the best result that I have is this:
https://user-images.githubusercontent.com/824198/50566568-7b49e400-0d22-11e9-884f-89720899de3a.png
I'm sure my mock is working because if I pass it through parameter to the "Myclass.query" it works perfectly.
I'm also sure that the mock is arriving in my test file, because the mock function is present in the fetch module.
But... all together aren't working 😭.
I created a very simple and small project to see this problem happening:
https://github.com/cesarjr/test-node-fetch
Can anyone help me?
Jest uses the mock at __mocks__/node-fetch.js any time node-fetch is required during the test.
The problem is that the first thing fetch-mock does is require node-fetch.
This means that Request is not defined when it is set on the config here, so calling prototype on the undefined Request causes an error here.
Someone smarter than me might know how to force Jest to require the actual node-fetch when fetch-mock requires node-fetch in the mock for node-fetch, but from what I can see it doesn't look like it is possible.
Looks like you will have to delete the mock at __mocks__/node-fetch.js and pass fetch to your code, something like this:
myclass.js
class MyClass {
static query(fetch, sessionId, query) {
const url = 'https://api.foobar.com';
const body = {
sessionId,
query
};
return fetch(url, {
method: 'post',
body: JSON.stringify(body)
})
.then(res => res.json());
}
}
module.exports = MyClass;
...then create the sandbox in your test and pass it to your code, something like this:
myclass.test.js
const fetch = require('fetch-mock').sandbox();
const MyClass = require('./myclass');
describe('MyClass', () => {
describe('.query', () => {
it('returns the response', () => {
fetch.mock('*', {'result': {'fulfillment': {'speech': 'The answer'}}});
expect.assertions(1);
return MyClass.query(fetch, '123', 'the question').then((data) => {
expect(data.result.fulfillment.speech).toBe('The answer'); // SUCCESS
});
});
});
});
I've now found a reliable way to combine fetch-mock and jest http://www.wheresrhys.co.uk/fetch-mock/#usageusage-with-jest

Resources