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

NestJS+Jest下单元测试是否多余?测试必要性存疑

单元测试是否多余?基于NestJS+Jest的场景分析

我目前使用NestJS作为开发框架,采用Jest执行测试。当前的问题是,编写的单元测试看起来非常简单,而且由于需要Mock数据库,无法真正测试数据库的实际行为。具体代码与场景如下:

UserService 代码

@Injectable()
export default class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  public async getUserById(id: number): Promise<User> {
    const user = await this.userRepository.byId(id);

    if (!user) {
      throw new UserNotFoundByIdError(id);
    }

    return user;
  }

  public async deleteUserById(id: number): Promise<void> {
    const user = await this.getUserById(id);

    await this.userRepository.deleteUser(user);
  }
}

UserRepository 代码

@Injectable()
export default class UserRepository {
  constructor(
    @InjectRepository(User) private readonly repo: Repository<User>,
  ) {}

  public byId(id: number): Promise<User> {
    return this.repo.findOne({ where: { id }});
  }

  public async deleteUser(user: User): Promise<User> {
    await this.repo.delete(user.id);
  }
}

单元测试代码(tests/unit目录)

const userRepositoryMock = createMockInstance(UserRepository);
const userService = new UserService(userRepositoryMock);

beforeEach(() => {
  jest.clearAllMocks();
  jest.restoreAllMocks();
  jest.resetAllMocks();
})

describe('UserService', () => {
  describe('getUserById', () => {
    it('should return the provided user from the repository', async () => {
      (userRepositoryMock.byId as jest.Mock).mockImplementationOnce(() => Promise.resolve({...new User(), id: 1}))

      const user = await userService.getUserById(1);

      expect(user).toBeDefined();
      expect(user.id).toEqual(1);
    });

    it('should throw a UserNotFoundByIdError if the repository returns a false value', async () => {
      await expect(() => userService.getUserById(1)).rejects.toThrow(UserNotFoundByIdError)
    });
  });

  describe('deleteUserById', () => {
    it('should delete the user fromt he database', () => {
      (userRepositoryMock.byId as jest.Mock).mockImplementationOnce(() => Promise.resolve({...new User(), id: 1}));

      await userService.deleteUserById(1);

      expect(userRepositoryMock.deleteUser).toHaveBeenCalled();
    });

    it('should throw a UserNotFoundByIdError if the user does not exist', async () => {
      await expect(() => userService.deleteUserById(1)).rejects.toThrow(UserNotFoundByIdError)
    });
  });
});

集成测试代码

let app: INestApplication;
let userService: UserService;
let userRepository: UserRepository;

beforeAll(async () => {
  const testModule = await Test.createTestingModule({
    imports: [
      databaseProvider,
      UserModule,
    ],
  }).compile();

  app = testModule.createNestApplication();

  userService = app.get(UserService);
  userRepository = app.get(UserRepository);
});

beforeEach(async () => {
  // clear database...
});

afterAll(async () => {
  await app.close();
});

describe('UserService', () => {
  describe('getUserById', () => {
    it('should return the provided user from the repository', async () => {
      const createdUser = await userRepository.createUser({ name: 'foo' });

      const user = await userService.getUserById(createdUser.id);

      expect(user).toBeDefined();
      expect(user.id).toEqual(createdUser.id);
    });

    it('should throw a UserNotFoundByIdError if the repository returns a false value', async () => {
      await expect(() => userService.getUserById(1)).rejects.toThrow(UserNotFoundByIdError)
    });
  });

  describe('deleteUserById', () => {
    it('should delete the user fromt he database', () => {
      const createdUser = await userRepository.createUser({ name: 'foo' });

      await userService.deleteUserById(createdUser.id);

      await expect(() => userService.getUserById(createdUser.id)).rejects.toThrow(UserNotFoundByIdError);
    });

    it('should throw a UserNotFoundByIdError if the user does not exist', async () => {
      await expect(() => userService.deleteUserById(1)).rejects.toThrow(UserNotFoundByIdError)
    });
  });
});

我的设计仅将数据传递给服务类,上层没有其他逻辑——如果上层需要更多功能,我会创建单独的服务处理复杂逻辑,因此上层测试没有实际功能可测。现在觉得单元测试非常多余,因为它只操作Mock实体,且结构简单,编写时全是模板代码,几乎测试不到实际逻辑。我是不是忽略了什么,或者对这个问题的理解有误?


你的理解没大问题,但单元测试仍有存在价值

1. 快速反馈与迭代效率

集成测试依赖真实数据库,跑起来慢,用例越多耗时越长。单元测试能在几秒内跑完,修改代码后立刻知道逻辑是否断裂——比如你改了getUserById里的空值判断逻辑,单元测试能第一时间发现问题,不用等集成测试启动、清库、造数据这一套流程。

2. 精准定位问题

集成测试失败时,你得排查是服务层逻辑错了,还是数据库连接、Repository实现、数据初始化出了问题。单元测试只测服务层,失败了直接锁定是服务层的逻辑问题,不用在整个链路里找bug。

3. 覆盖极端边界场景

有些场景在集成测试里很难构造,比如Repository返回null、抛出数据库连接异常等,单元测试可以轻松Mock这些情况,确保服务层能正确处理。比如你可以MockuserRepository.byId抛出异常,测试服务层是否正确捕获或向上抛出(如果需要的话)。

4. 代码重构保障

如果以后要重构服务层逻辑——比如调整deleteUserById里的调用顺序,单元测试能帮你确认重构后的逻辑和原来一致,不用重新跑一遍集成测试。

可以优化单元测试的写法,减少模板代码

你现在的单元测试确实太模板化,可以做这些优化:

  • 复用Mock逻辑:把常用的Mock(比如返回存在的用户、返回null)抽成辅助函数,避免重复写mockImplementationOnce。
  • 聚焦核心逻辑:不用写那种“返回用户”的冗余用例,重点测服务层的特有逻辑——也就是空值判断、异常抛出、依赖调用顺序这些。比如deleteUserById里,确保先调用getUserById,再调用deleteUser,这是集成测试不会直接覆盖的服务层内部流程。

什么时候可以减少单元测试?

如果你的服务层完全是Repository的透传,没有任何自定义逻辑(比如连空值判断都放到Repository里),那单元测试确实意义不大。但目前你的服务层有异常抛出、调用依赖的顺序逻辑,这些还是值得用单元测试覆盖的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 04:44:55