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

如何为依赖Repository<entity>的Auth服务编写测试?解决循环JSON错误

测试AuthService时出现循环引用转JSON错误

运行测试时持续出现以下错误:

TypeError: Converting circular structure to JSON
        --> starting at object with constructor 'NestContainer'
        |     property 'modules' -> object with constructor 'ModulesContainer'
        |     property 'InternalCoreModule' -> object with constructor 'Module'
        --- property 'container' closes the circle
        at stringify (<anonymous>)

相关代码

auth.service.spec.ts

import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from '../users/users.service';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';
import { jwtConstants } from './strategies/constants';
import { JwtStrategy } from './strategies/jwt.strategy';

describe('AuthService', () => {
  let service: AuthService;

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      imports: [
        UsersModule,
        PassportModule,
        JwtModule.register({
          secret: jwtConstants.secret,
          signOptions: { expiresIn: '60s' },
        }),
      ],
      providers: [
        AuthService, 
        JwtStrategy,
        UsersService
    ],
    }).compile();

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

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

auth.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Users } from '../db/entities/users.entity';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';

export class User {
  username: string;
  password: string;
}

@Injectable()
export class AuthService {
  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
  ) {}

  async validateUser(
    username: string,
    password: string,
  ): Promise<Users | undefined> {
    const user = await this.usersService.findOne(username);

    const isPasswordCorrect = await bcrypt.compare(
      password,
      user?.passwordHash,
    );

    if (isPasswordCorrect) {
      return user;
    }
    return null;
  }

  async login(userRequest: any) {
    const response = await this.validateUser(
      userRequest.username,
      userRequest.password,
    );

    if (response && response !== undefined) {
      const payload = {
        username: response?.userName,
        sub: response?.id,
      };

      return {
        access_token: this.jwtService.sign(payload),
      };
    }

    throw new NotFoundException(
      'This combination of user and password does not exist.',
    );
  }
}

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from '../db/entities/users.entity';
import { Repository } from 'typeorm';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(Users)
    private userRepository: Repository<Users>,
  ) {}

  async findOne(username: string): Promise<Users | undefined> {
    return this.userRepository.findOneBy({
      userName: username,
    });
  }
}

解决方案

错误根源是测试模块同时导入UsersModule并声明UsersService为provider,导致Nest容器出现循环引用;同时真实UsersService依赖TypeORM的Repository,测试环境未正确配置数据库连接,进一步引发问题。

修改步骤:

  1. 移除UsersModule导入,避免加载不必要的依赖和循环
  2. 模拟UsersService,替代真实服务以避免依赖数据库操作
  3. 调整测试模块的providers配置,替换真实UsersService为模拟对象

修改后的auth.service.spec.ts代码:

import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from '../users/users.service';
import { AuthService } from './auth.service';
import { jwtConstants } from './strategies/constants';
import { JwtStrategy } from './strategies/jwt.strategy';

// 模拟UsersService
const mockUsersService = {
  findOne: jest.fn().mockResolvedValue({
    id: 1,
    userName: 'testuser',
    passwordHash: '$2b$10$exampleHash', // 提前加密的测试密码
  }),
};

describe('AuthService', () => {
  let service: AuthService;
  let usersService: UsersService;

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      imports: [
        PassportModule,
        JwtModule.register({
          secret: jwtConstants.secret,
          signOptions: { expiresIn: '60s' },
        }),
      ],
      providers: [
        AuthService,
        JwtStrategy,
        // 提供模拟的UsersService
        {
          provide: UsersService,
          useValue: mockUsersService,
        },
      ],
    }).compile();

    service = moduleRef.get<AuthService>(AuthService);
    usersService = moduleRef.get<UsersService>(UsersService);
  });

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

  // 可添加更多测试用例,比如验证validateUser和login方法
  it('should validate user correctly', async () => {
    const result = await service.validateUser('testuser', 'testpassword');
    expect(result).toBeDefined();
    expect(usersService.findOne).toHaveBeenCalledWith('testuser');
  });
});

额外说明

  • 可根据测试需求调整模拟UsersService的返回值,比如模拟用户不存在的场景,测试登录失败逻辑
  • 若需更复杂的模拟,可使用jest.spyOn替换特定方法,而非完全替换整个服务
  • 确保bcrypt在测试环境正常工作,也可模拟bcrypt.compare方法,避免实际加密操作

内容的提问来源于stack exchange,提问作者Miguel Angel Segura Sandoval

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 05:35:03