Cannot read properties of undefined (reading 'toPromise') in a mocked function

Viewed 404

I am trying to test a Nest.js service that uses Clickhouse as the database. Since this is a unit test, I don't want it to actually go to the database and do stuff. Just want to mock some returns to see how the service reacts.

So, I mocked up the whole database library:

jest.mock('@depyronick/nestjs-clickhouse');

And then injected a fake 'database' into the test application:

 {
    provide: 'database',
    useValue: {
        insert: jest.fn(),
        query: jest.fn(),
    },
 },

In my application, I have a create new account function that first checks if an account exists with the same id (slug). If the account exists, it shouldn't create and should return an error. If it doesn't exist, then it should proceed to creating it.

The first test runs fine. But the second one if breaking and returning:

TypeError: Cannot read properties of undefined (reading 'toPromise')

I am mocking them in the exact same way. Can't understand why the second one is failing. The error above is returning on the getBySlug function, which, as I understand, should not even be called since it's being mocked.

My files:

accounts.service.ts

    import { HttpException, HttpStatus, Inject, Injectable, Logger } from '@nestjs/common';
    import { Account } from './entities/account.entity';
    import * as moment from 'moment';
    import * as newrelic from 'newrelic';
    import { ClickHouseClient } from '@depyronick/nestjs-clickhouse';
    import { v4 as uuidv4 } from 'uuid';
    
    @Injectable()
    export class AccountsService {
        private readonly logger = new Logger('Accounts Service');
        constructor(@Inject('database') private readonly db: ClickHouseClient) {}
    
        async createAccount(args): Promise<Account> {
            if (await this.getBySlug(args.slug)) {
                throw new HttpException('Account name already exists', HttpStatus.BAD_REQUEST);
            }
    
            const id = uuidv4();
            const now = new Date();
    
            await this.db
                .insert('accounts', [
                    {
                        id: id,
                        slug: args.slug,
                        name: args.name,
                        createdAt: moment.utc(now).format('YYYY-MM-DD HH:mm:ss'),
                    },
                ])
                .toPromise();
    
            const account: Account = { id: id, slug: args.slug, name: args.name, createdAt: now };
    
            newrelic.recordCustomEvent('accountCreated', { accountId: id, slug: account.slug, name: account.name });
    
            return account;
        }
    
        async getBySlug(slug: string): Promise<Account> {
            return await this.db.query<Account>(`SELECT * FROM accounts where slug='${slug}' limit 1`).toPromise();
        }
    
        async getById(id: string): Promise<Account> {
            return await this.db.query<Account>(`SELECT * FROM accounts where id='${id}' limit 1`).toPromise();
        }
    }

accounts.service.spec.ts

    import { Test, TestingModule } from '@nestjs/testing';
    import { Account } from '../accounts/entities/account.entity';
    import { AccountsService } from '../accounts/accounts.service';
    import { ClickHouseClient } from '@depyronick/nestjs-clickhouse';
    
    const now = new Date();
    
    jest.mock('@depyronick/nestjs-clickhouse');
    
    const fakeAccount: Account = {
        id: 'lalal',
        slug: 'palmeiras',
        name: 'Palmeiras',
        createdAt: now,
    };
    
    jest.mock('newrelic', () => {
        return {
            recordMetric: jest.fn(),
            recordCustomEvent: jest.fn(),
        };
    });
    
    describe('AccountsService', () => {
        let service: AccountsService;
    
        beforeEach(async () => {
            const module: TestingModule = await Test.createTestingModule({
                providers: [
                    AccountsService,
                    {
                        provide: 'database',
                        useValue: {
                            insert: jest.fn(),
                            query: jest.fn(),
                            queryPromise: jest.fn(),
                        },
                    },
                ],
            }).compile();
    
            service = module.get<AccountsService>(AccountsService);
        });
    
        it('should be defined', () => {
            expect(service).toBeDefined();
        });
    
        describe('creating an account', () => {
            it('should create account if not exists', async () => {
                const findAll = jest.spyOn(service, 'getBySlug').mockImplementation(() => Promise.resolve(null));
                const insert = (ClickHouseClient.prototype.insert = jest.fn());
    
                const event = await service.createAccount(fakeAccount.name);
                expect(findAll).toHaveBeenCalled();
                expect(insert).toHaveBeenCalled();
                expect(event.name).toEqual(fakeAccount.name);
                expect(event.createdAt).toEqual(fakeAccount.createdAt);
            });
        });
    
        describe('should not create an account that already exists', () => {
            it('should return error if account exists', async () => {
                const findAll = jest.spyOn(service, 'getBySlug').mockImplementation(() => Promise.resolve(fakeAccount));
                const insert = (ClickHouseClient.prototype.insert = jest.fn());
    
                service
                    .createAccount(fakeAccount.name)
                    .then((result) => {
                        expect(result).toHaveLength(0);
                    })
                    .catch((err) => {
                        expect(findAll).toHaveBeenCalled();
                        expect(insert).not.toHaveBeenCalled();
                        expect(err).toBeTruthy();
                        expect(err.message).toContain('already exists');
                    });
            });
        });
    });

What am I missing, guys?

1 Answers

Figure it out with help from @jmcdo29 from the Nest Discord forum.

{
    provide: 'database',
    useValue: {
        insert: jest.fn().mockReturnValue({
            toPromise: jest.fn().mockResolvedValue(insertResult),
        })
        query: jest.fn(),
    },
 },
Related