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

如何在Jest单元测试中提供自定义类型S3ServiceConstructor

问题

编写S3Service的Jest单元测试时,尝试为S3Service提供自定义类型S3ServiceConstructor并配置到providers中出现报错,求正确配置方式。

S3服务代码

import { IAWS } from '../AWS';
import { S3 } from 'aws-sdk';
export interface S3ServiceConstructor {
  pathDelimiter?: string;
}

export class S3Service {
  private s3: S3;
  private readonly pathDelimiter: string;

  constructor(
    { pathDelimiter }: S3ServiceConstructor,
    options?: S3.Types.ClientConfiguration
  ) {
    this.pathDelimiter = pathDelimiter || '/';
    this.s3 = new IAWS.S3(options);
  }
}

测试文件代码

import { Test } from '@nestjs/testing';

export interface S3ServiceConstructor {
    pathDelimiter?: string;
}

import { S3Service } from './s3.service';

describe('S3 Service', () => {
    let service: S3Service;

    const mockS3Instance = {
        upload: jest.fn().mockReturnThis(),
        promise: jest.fn().mockReturnThis(),
        catch: jest.fn()
    }

    jest.mock('aws-sdk', () => {
        return jest.fn(() => mockS3Instance)
    });

    beforeEach(async () => {
        const moduleRef = await Test.createTestingModule({
            providers: [
                S3Service,
                provide: [] // 此处配置?
            ]
        }).compile();

        service = moduleRef.get<S3Service>(S3Service);
    });

    afterEach(() => {
        jest.clearAllMocks();
    });

    it('getXYZ should be called with correct parameters', async () => {
        // ....
    });
});

正确配置方式

方案一:适配Nest依赖注入(推荐长期维护)

  1. 清理重复类型:删掉测试文件里重复定义的S3ServiceConstructor,直接从s3.service导入,避免类型冲突。
  2. 给构造参数加注入标识:TypeScript接口编译后会消失,Nest无法识别接口作为注入token,得给S3Service的构造参数加@Inject装饰器,用字符串作为唯一标识。
  3. 正确Mock依赖的IAWS.S3:原mock没覆盖IAWS.S3,调整mock逻辑让new IAWS.S3()返回预设的mock实例。

修改后的S3Service代码

import { Inject, Optional } from '@nestjs/common';
import { IAWS } from '../AWS';
import { S3 } from 'aws-sdk';
export interface S3ServiceConstructor {
  pathDelimiter?: string;
}

export class S3Service {
  private s3: S3;
  private readonly pathDelimiter: string;

  constructor(
    @Inject('S3_SERVICE_OPTIONS') { pathDelimiter }: S3ServiceConstructor,
    @Optional() @Inject('S3_CLIENT_CONFIG') options?: S3.Types.ClientConfiguration
  ) {
    this.pathDelimiter = pathDelimiter || '/';
    this.s3 = new IAWS.S3(options);
  }
}

对应的测试文件代码

import { Test } from '@nestjs/testing';
import { S3Service, S3ServiceConstructor } from './s3.service';

// Mock IAWS模块,覆盖S3构造函数
jest.mock('../AWS', () => ({
  IAWS: {
    S3: jest.fn(() => ({
      upload: jest.fn().mockReturnThis(),
      promise: jest.fn().mockResolvedValue({ Location: 'test-url' }),
      // 按需添加其他需要mock的方法
    })),
  },
}));

describe('S3 Service', () => {
  let service: S3Service;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        S3Service,
        // 提供S3_SERVICE_OPTIONS的具体配置值
        {
          provide: 'S3_SERVICE_OPTIONS',
          useValue: { pathDelimiter: '-' } as S3ServiceConstructor,
        },
        // 可选的S3客户端配置,标记为optional避免启动报错
        {
          provide: 'S3_CLIENT_CONFIG',
          useValue: { region: 'us-east-1' },
          optional: true,
        },
      ],
    }).compile();

    service = moduleRef.get<S3Service>(S3Service);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should initialize with correct path delimiter', () => {
    // 可以给S3Service加个getter方法暴露私有属性,比如:
    // getPathDelimiter() { return this.pathDelimiter; }
    // expect(service.getPathDelimiter()).toBe('-');
  });

  it('getXYZ should be called with correct parameters', async () => {
    // 你的测试逻辑
  });
});

方案二:直接实例化服务(快速测试无需改原代码)

如果不想改动原S3Service的代码,直接在测试里用useFactory手动创建实例,传入构造参数即可:

import { Test } from '@nestjs/testing';
import { S3Service } from './s3.service';

// Mock IAWS模块,覆盖S3构造函数
jest.mock('../AWS', () => ({
  IAWS: {
    S3: jest.fn(() => ({
      upload: jest.fn().mockReturnThis(),
      promise: jest.fn().mockResolvedValue({ Location: 'test-url' }),
    })),
  },
}));

describe('S3 Service', () => {
  let service: S3Service;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        {
          provide: S3Service,
          useFactory: () => {
            // 手动传入构造参数
            return new S3Service({ pathDelimiter: '-' }, { region: 'us-east-1' });
          },
        },
      ],
    }).compile();

    service = moduleRef.get<S3Service>(S3Service);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('getXYZ should be called with correct parameters', async () => {
    // 测试逻辑
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 02:25:39