You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS+TypeScript单元测试报Cannot read property 'getById' of undefined

问题描述

工作中使用NestJS开发,对单元测试不熟练,开发的图片CRUD操作模块的单元测试无法运行,相关代码如下:

media.gateway.ts

import { Injectable } from '@nestjs/common';
import { MediaEntity } from '../entities/media.entity';
import { DBService } from '../../services/db/db.service';
import { NotFoundException } from '../../exceptions/not-found.exception';

@Injectable()
export class MediaGateway {
    private mediaTable: string = 'media';

    constructor(
        private readonly db: DBService,
    ) { }

    public async getById(mediaId: number): Promise<MediaEntity> {
        const mediaRaw: any = await this.db.fetchOne('\
            SELECT id, name, type, private, created, users_id, media_storage_id \
            FROM ' + this.mediaTable + ' \
            WHERE id = ?',
        [ mediaId ]);

        if (!mediaRaw) {
            throw new NotFoundException('Media not found');
        }

        return this.toMediaEntity(mediaRaw);
    }

    private toMediaEntity(media: any): MediaEntity {
        return new MediaEntity(
            media.id,
            media.name,
            media.type ? media.type.toString() : null,
            media.private === 1,
            media.media_storage_id,
            media.users_id,
            new Date(media.created),
        );
    }
}

单元测试代码 media.gateway.spec.ts

import { Fixture } from '../../../../test/fixture';
import { Test } from '@nestjs/testing';
import { DBServiceTestProvider } from '../../../../test/db.service.test.provider';
import { MediaEntity, MediaType } from '../entities/media.entity';
import { MediaGateway } from './media.gateway';

describe('Media Gateway', () => {

    let gateway: MediaGateway;

    beforeEach(async () => {
        await Fixture.clearAndInit();

        const module = await Test.createTestingModule({
            providers: [
                MediaGateway,
                DBServiceTestProvider,
            ],
        }).compile();

        gateway = module.get<MediaGateway>(MediaGateway);
    });

    describe('Get existing media by id', async () => {
        it('Should return media entity', async () => {
            const media: MediaEntity = await gateway.getById(1);
            expect(media).toMatchObject(new MediaEntity(
                1,
                'Media Name',
                MediaType.PHOTO,
                false,
                1,
                1,
                new Date('2019-04-25 19:00:05'),
            ));
        });
    });
});

首次运行报错

TypeError: Cannot read property 'getById' of undefined

25 |     describe('Get existing media by id', async () => {
26 |         it('Should return media entity', async () => {
> 27 |             const media: MediaEntity = await gateway.getById(1);
   |                                                      ^
28 |             expect(media).toMatchObject(new MediaEntity(
29 |                 1,
30 |                 'Media Name',

VSCode无代码错误提示,media.gateway.ts引用路径正确。

补充简化测试代码

import { Fixture } from '../../../../test/fixture';
import { Test } from '@nestjs/testing';
import { DBServiceTestProvider } from '../../../../test/db.service.test.provider';
import { MediaEntity, MediaType } from '../entities/media.entity';
import { MediaGateway } from './media.gateway2';

describe('Media Gateway', () => {

    let gateway: MediaGateway;

    beforeEach(async () => {
        await Fixture.clearAndInit();

        const module = await Test.createTestingModule({
            providers: [
                MediaGateway,
                DBServiceTestProvider,
            ],
        }).compile();

        gateway = module.get<MediaGateway>(MediaGateway);
    });

    it('should be defined', () => {
        expect(gateway).toBeDefined();
    });
});

简化测试运行报错

> tslint -p tsconfig.json -c tslint.json && jest --no-cache --runInBand "src/core/medias/gateways/media.gateway2.spec.ts"                                                                                                          
                                                                                                                                                                                                                                   
 FAIL  src/core/medias/gateways/media.gateway2.spec.ts (6.054s)                                                                                                                                                                    
  Media Gateway                                                                                                                                                                                                                    
    ✕ should be defined (64ms)                                                                                                                                                                                                     
                                                                                                                                                                                                                                   
  ● Media Gateway › should be defined                                                                                                                                                                                              
                                                                                                                                                                                                                                   
    ER_DUP_KEYNAME: Duplicate key name 'value_UNIQUE'

      at Query.Object.<anonymous>.Sequence._packetToError (../node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)
      at Query.Object.<anonymous>.Query.ErrorPacket (../node_modules/mysql/lib/protocol/sequences/Query.js:79:18)
      at Protocol.Object.<anonymous>.Protocol._parsePacket (../node_modules/mysql/lib/protocol/Protocol.js:291:23)
      at Parser._parsePacket (../node_modules/mysql/lib/protocol/Parser.js:433:10)
      at Parser.write (../node_modules/mysql/lib/protocol/Parser.js:43:10)
      at Protocol.Object.<anonymous>.Protocol.write (../node_modules/protocol/Protocol.js:38:16)
      at Socket.<anonymous> (../node_modules/mysql/lib/Connection.js:88:28)
      at Socket.<anonymous> (../node_modules/mysql/lib/Connection.js:526:10)
          --------------------
      at Protocol.Object.<anonymous>.Protocol._enqueue (../node_modules/mysql/lib/protocol/Protocol.js:144:48)
      at Connection.query (../node_modules/mysql/lib/Connection.js:198:25)
      at Promise (../node_modules/postgrator/lib/MysqlClient.js:57:20)
      at MysqlClient._runQuery (../node_modules/postgrator/lib/MysqlClient.js:56:12)
      at MysqlClient.runQuery (../node_modules/postgrator/lib/Client.js:58:19)
      at sequence.then.then.then (../node_modules/postgrator/postgrator.js:217:37)

  ● Media Gateway › should be defined

    expect(received).toBeDefined()

    Received: undefined

      24 |
      25 |     it('should be defined', () => {
    > 26 |         expect(gateway).toBeDefined();
         |                         ^
      27 |     });
      28 |     // describe('Get existing media by id', async () => {
      29 |     //     it('Should return media entity', async () => {

      at Object.it (core/medias/gateways/media.gateway2.spec.ts:26:25)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        6.078s
解决方案

根因分析

你的问题和MediaGateway本身的代码、引用路径无关,问题出在beforeEach生命周期的执行流程:
await Fixture.clearAndInit()执行时抛出了数据库错误,直接中断了后续逻辑,测试模块没有完成编译,gateway变量从未被赋值,所以才会报undefined相关错误。报错中首先出现的ER_DUP_KEYNAME: Duplicate key name 'value_UNIQUE'是明确的MySQL数据库错误,指建表/加索引时重复创建了同名的唯一键。

解决步骤

1. 修复数据库初始化错误

这是优先级最高的问题:

  • 检查你的数据库迁移脚本,确认是否有重复定义value_UNIQUE唯一键的逻辑,比如同一个表的建表语句中两次添加同名唯一索引,或者加索引的语句没有做存在性判断。
  • 检查Fixture.clearAndInit()的实现逻辑,确认是否真的完整清空了测试库的所有表、包括迁移工具的记录表(比如postgrator的schemaversion表),如果漏删迁移记录表,会导致每次初始化都重复执行所有迁移脚本,触发重复建索引的错误。
  • 可以手动清空测试库的所有表,再重新运行测试验证错误是否消失。

2. 验证模块实例化逻辑

可以临时注释掉初始化逻辑,排除数据库的影响,确认MediaGateway的实例化是否正常:

beforeEach(async () => {
    // 临时注释,验证模块逻辑
    // await Fixture.clearAndInit();
    const module = await Test.createTestingModule({
        providers: [
            MediaGateway,
            DBServiceTestProvider,
        ],
    }).compile();
    gateway = module.get<MediaGateway>(MediaGateway);
});

如果注释后gateway可以正常实例化,即可完全确认问题出在Fixture初始化流程。

3. 单元测试优化建议

如果只是对MediaGateway做单元测试,不需要依赖真实数据库,可以直接mock DBService,避免这类数据库环境问题,同时提升测试运行速度:

// 模拟DBService返回值
const mockDBService = {
  fetchOne: jest.fn().mockResolvedValue({
    id: 1,
    name: 'Media Name',
    type: MediaType.PHOTO,
    private: 0,
    media_storage_id: 1,
    users_id: 1,
    created: '2019-04-25 19:00:05'
  })
}

const module = await Test.createTestingModule({
  providers: [
    MediaGateway,
    // 替换真实的DBService为模拟对象
    { provide: DBService, useValue: mockDBService }
  ]
}).compile();

内容的提问来源于stack exchange,提问作者djchals

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 21:36:03