NestJs v9.0.0、ioredis v5.3.2、jest v29.5.0。
我无法正确设置我的 redis 服务以使其在 Jest 单元测试或启动 Nest 应用程序中工作。我有一个服务 RedisService
,它从“ioredis”导入 Redis。
在为 RedisService
运行单元测试( Jest )时遇到问题,或者如果我修复了这些问题,那么在启动 Nest 时会出现以下错误:
错误#1
启动 Nest 或运行 e2e 时:
Nest can't resolve dependencies of the RedisService (?). Please make sure that the argument Redis at index [0] is available in the RedisModule context.
Potential solutions:
- Is RedisModule a valid NestJS module?
- If Redis is a provider, is it part of the current RedisModule?
- If Redis is exported from a separate @Module, is that module imported within RedisModule?
@Module({
imports: [ /* the Module containing Redis */ ]
})
启动应用程序或运行 e2e 测试时会重现上述错误。
这是我的 RedisService
,单元测试可以正常工作,但是在启动应用程序或运行 e2e 测试时,我收到错误 #1:
import { Injectable, OnModuleDestroy } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisService implements OnModuleDestroy {
constructor(private client: Redis) {} // <-- This is possibly the "issue". Unit tests work fine with this DI but app and e2e fail
async onModuleInit() {
this.client = new Redis({
host: process.env.REDIS_HOST,
port: +process.env.REDIS_PORT,
});
}
async onModuleDestroy() {
await this.client.quit();
}
async set(key: string, value: string, expirationSeconds: number) {
await this.client.set(key, value, 'EX', expirationSeconds);
}
async get(key: string): Promise<string | null> {
return await this.client.get(key);
}
}
我尝试了不同的方法,这是单元测试最终工作正常的方法,但在运行 e2e 测试或启动应用程序时显然不行。
但是,我可以通过使我的 RedisService 不将“ioredis”中的 Redis 注入(inject)到构造函数中,而是在 onModuleInit 生命周期 Hook 中实例化它来轻松修复此问题。但是如果我停止将它注入(inject)到构造函数中,那么它的单元测试就会失败,因为 redisClient 是一个空对象,而不是我想要的模拟对象。这会导致修复错误 #1,但会出现如下所述的错误 #2。
错误#2
如果测试失败,我会收到以下错误:
类型错误:无法读取未定义的属性(读取“设置”)
和类型错误:无法读取未定义的属性(读取“get”)
单元测试会失败,但如果我将 redis.service.ts
更改为:
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisService implements OnModuleInit, OnModuleDestroy {
private client: Redis; // no injection in the constructor
async onModuleInit() {
this.client = new Redis({
host: process.env.REDIS_HOST,
port: +process.env.REDIS_PORT,
});
}
// ...
}
然后测试失败,因为 redisService 是一个空对象。
上下文
这些是规范,redis.service.spec.ts
:
import { Test, TestingModule } from '@nestjs/testing';
import Redis from 'ioredis';
import * as redisMock from 'redis-mock';
import { RedisService } from './redis.service';
describe('RedisService', () => {
let service: RedisService;
let redisClientMock: redisMock.RedisClient;
beforeEach(async () => {
redisClientMock = {
set: jest.fn(),
get: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
RedisService,
{
provide: Redis,
useValue: redisMock.createClient(),
},
],
}).compile();
redisClientMock = module.get(Redis);
service = module.get<RedisService>(RedisService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('set', () => {
it('should set a value in Redis with expiration date', async () => {
const spy = jest.spyOn(redisClientMock, 'set');
await service.set('my-key', 'my-value', 60);
expect(spy).toHaveBeenCalledWith('my-key', 'my-value', 'EX', 60);
});
});
describe('get', () => {
it('should return null if the key does not exist', async () => {
const spy = jest.spyOn(redisClientMock, 'get').mockReturnValue(undefined);
const value = await service.get('nonexistent-key');
expect(value).toBeUndefined();
});
it('should return the value if the key exists', async () => {
jest.spyOn(redisClientMock, 'get').mockReturnValue('my-value');
const value = await service.get('my-key');
expect(value).toBe('my-value');
});
});
});
这是我的
redis.module.ts
:
import { Module } from '@nestjs/common';
import { RedisService } from './redis.service';
@Module({
providers: [RedisService],
exports: [RedisService],
})
export class RedisModule {}
RedisModule 位于作为依赖项的模块的导入数组中。
我想使用 ioredis 我们只需避免将其注入(inject)到构造函数中,但是如何修复 redis.service.spec.ts
以便它按时获取 redisClient 呢?它应该作为构造函数中的依赖项注入(inject)吗?无论如何,Redis 应该如何在 Nest 中实现,以便 e2e 和单元测试都能顺利进行?
最佳答案
尝试不同的方法后修复了它。使用此命令 NEST_DEBUG=true npm test
运行单元测试帮助我最终缩小了问题范围,直到单元测试成功运行。修复它的事情:
- 像这样创建文件
redis.provider.ts
:
import { Provider } from '@nestjs/common';
import Redis from 'ioredis';
export type RedisClient = Redis;
export const redisProvider: Provider = {
useFactory: (): RedisClient => {
return new Redis({
host: 'localhost',
port: 6379,
});
},
provide: 'REDIS_CLIENT',
};
- 在模块中提供它,在我的例子中,
redis.module.ts
:
import { Module } from '@nestjs/common';
import { redisProvider } from './redis.providers';
import { RedisService } from './redis.service';
@Module({
providers: [redisProvider, RedisService],
exports: [RedisService],
})
export class RedisModule {}
- 在服务
redis.service.ts
中,将其注入(inject)到构造函数中,如下所示:
import { Inject, Injectable } from '@nestjs/common';
import { RedisClient } from './redis.providers';
@Injectable()
export class RedisService {
public constructor(
@Inject('REDIS_CLIENT')
private readonly client: RedisClient,
) {}
async set(key: string, value: string, expirationSeconds: number) {
await this.client.set(key, value, 'EX', expirationSeconds);
}
async get(key: string): Promise<string | null> {
return await this.client.get(key);
}
}
- 最后是测试,
redis.service.spec.ts
:使用字符串REDIS_CLIENT
,而不是从ioredis
导入的Redis。所以现在看起来像这样:
import { Test, TestingModule } from '@nestjs/testing';
import Redis from 'ioredis';
import * as redisMock from 'redis-mock';
import { RedisService } from './redis.service';
describe('RedisService', () => {
let service: RedisService;
let redisClientMock: redisMock.RedisClient;
beforeEach(async () => {
redisClientMock = {
set: jest.fn(),
get: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
RedisService,
{
provide: 'REDIS_CLIENT',
useValue: redisMock.createClient(),
},
],
}).compile();
redisClientMock = module.get('REDIS_CLIENT');
service = module.get<RedisService>(RedisService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
关于typescript - 如何使用 `ioredis`中的Redis设置RedisService?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76308716/