我想进行单元测试,并为我要测试的Nest API提供一些配置服务。启动应用程序时,我使用joi包验证环境变量。
我为数据库,服务器等提供了多种配置服务,因此我首先创建了基本服务。该程序能够读取环境变量,将原始字符串解析为所需的数据类型并验证该值。
import { ConfigService } from '@nestjs/config';
import { AnySchema, ValidationResult, ValidationError } from '@hapi/joi';
export abstract class BaseConfigurationService {
constructor(protected readonly configService: ConfigService) {}
protected constructValue(key: string, validator: AnySchema): string {
const rawValue: string = this.configService.get(key);
this.validateValue(rawValue, validator, key);
return rawValue;
}
protected constructAndParseValue<TResult>(key: string, validator: AnySchema, parser: (value: string) => TResult): TResult {
const rawValue: string = this.configService.get(key);
const parsedValue: TResult = parser(rawValue);
this.validateValue(parsedValue, validator, key);
return parsedValue;
}
private validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void {
const validationSchema: AnySchema = validator.label(label);
const validationResult: ValidationResult = validationSchema.validate(value);
const validationError: ValidationError = validationResult.error;
if (validationError) {
throw validationError;
}
}
}
现在,我可以使用多个配置服务来扩展此服务。为了简单起见,我将为此使用服务器配置服务。当前,它仅保留应用程序将侦听的端口。
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as Joi from '@hapi/joi';
import { BaseConfigurationService } from './base.configuration.service';
@Injectable()
export class ServerConfigurationService extends BaseConfigurationService {
public readonly port: number;
constructor(protected readonly configService: ConfigService) {
super(configService);
this.port = this.constructAndParseValue<number>(
'SERVER_PORT',
Joi.number().port().required(),
Number
);
}
}
我发现那里有多篇文章我应该只测试公共方法,例如
https://softwareengineering.stackexchange.com/questions/100959/how-do-you-unit-test-private-methods
因此,我假设我不应该测试基本配置服务中的方法。但是我想测试扩展基础服务的类。我从这个开始
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { ServerConfigurationService } from './server.configuration.service';
const mockConfigService = () => ({
get: jest.fn(),
});
describe('ServerConfigurationService', () => {
let serverConfigurationService: ServerConfigurationService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ServerConfigurationService,
{
provide: ConfigService,
useFactory: mockConfigService
}
],
}).compile();
serverConfigurationService = module.get<ServerConfigurationService>(ServerConfigurationService);
});
it('should be defined', () => {
expect(serverConfigurationService).toBeDefined();
});
});
但是,正如您在第二个代码片段中所看到的那样,我是从构造函数中的基本服务调用函数的。测试立即失败
ValidationError:“ SERVER_PORT”必须为数字
尽管配置服务依赖于抽象基类和外部.env文件,但是有没有办法对它们进行单元测试?因为我知道我可以创建一个,mockConfigService
但是我认为基类可以解决这个问题。我不知道如何修复此测试文件。
主要问题归结为:您正在使用Joi库来解析环境变量。每当您调用时validateValue
,都会调用Joi函数,这些函数期望设置实际的环境变量(在这种情况下为SERVER_PORT
)。现在,需要设置这些环境变量是运行服务的有效假设。但是在您的测试案例中,您没有设置环境变量,因此Joi验证失败。
一个原始的解决方案是在中设置process.env.SERVER_PORT
一些值,beforeEach
然后在中将其删除afterEach
。但是,这只是解决实际问题的方法。
实际的问题是:您对库调用进行了硬编码BaseConfigurationService
,并假设已设置了环境变量。前面我们已经弄清,这不是运行测试时的有效假设。当您在编写测试时偶然发现像这样的问题时,通常会指出问题所在。
我们该如何解决?
BaseConfigurationService
。我们将该服务类称为ValidationService
。BaseConfigurationService
使用Nest的依赖项注入将该服务类注入。ValidationService
因此它不依赖于实际的环境变量,但是,例如,在验证过程中它不会抱怨任何东西。因此,这是我们一步一步实现的方法:
1.定义ValidationService接口
该接口仅描述需要验证值的类的外观:
import { AnySchema } from '@hapi/joi';
export interface ValidationService {
validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void;
}
2.实施ValidationService
现在,我们将从您的验证码中提取BaseConfigurationService
并使用它来实现ValidationService
:
import { Injectable } from '@nestjs/common';
import { AnySchema, ValidationResult, ValidationError } from '@hapi/joi';
@Injectable()
export class ValidationServiceImpl implements ValidationService {
validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void {
const validationSchema: AnySchema = validator.label(label);
const validationResult: ValidationResult = validationSchema.validate(value);
const validationError: ValidationError = validationResult.error;
if (validationError) {
throw validationError;
}
}
}
3.将ValidationServiceImpl注入BaseConfigurationService
现在,我们将从中删除验证逻辑BaseConfigurationService
,而是添加对的调用ValidationService
:
import { ConfigService } from '@nestjs/config';
import { AnySchema, ValidationResult, ValidationError } from '@hapi/joi';
import { ValidationServiceImpl } from './validation.service.impl';
export abstract class BaseConfigurationService {
constructor(protected readonly configService: ConfigService,
protected readonly validationService: ValidationServiceImpl) {}
protected constructValue(key: string, validator: AnySchema): string {
const rawValue: string = this.configService.get(key);
this.validationService.validateValue(rawValue, validator, key);
return rawValue;
}
protected constructAndParseValue<TResult>(key: string, validator: AnySchema, parser: (value: string) => TResult): TResult {
const rawValue: string = this.configService.get(key);
const parsedValue: TResult = parser(rawValue);
this.validationService.validateValue(parsedValue, validator, key);
return parsedValue;
}
}
4.实现一个模拟ValidationService
出于测试目的,我们不想针对实际的环境变量进行验证,而只是接受所有值。因此,我们实现了一个模拟服务:
import { ValidationService } from './validation.service';
import { AnySchema, ValidationResult, ValidationError } from '@hapi/joi';
export class ValidationMockService implements ValidationService{
validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void {
return;
}
}
5.调整扩展BaseConfigurationService
为已ConfigurationServiceImpl
注入的类,并将其传递给BaseConfigurationService
:
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as Joi from '@hapi/joi';
import { BaseConfigurationService } from './base.configuration.service';
import { ValidationServiceImpl } from './validation.service.impl';
@Injectable()
export class ServerConfigurationService extends BaseConfigurationService {
public readonly port: number;
constructor(protected readonly configService: ConfigService,
protected readonly validationService: ValidationServiceImpl) {
super(configService, validationService);
this.port = this.constructAndParseValue<number>(
'SERVER_PORT',
Joi.number().port().required(),
Number
);
}
}
6.在测试中使用模拟服务
最后,现在ValidationServiceImpl
是的依赖项BaseConfigurationService
,我们在测试中使用模拟版本:
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { ServerConfigurationService } from './server.configuration.service';
import { ValidationServiceImpl } from './validation.service.impl';
import { ValidationMockService } from './validation.mock-service';
const mockConfigService = () => ({
get: jest.fn(),
});
describe('ServerConfigurationService', () => {
let serverConfigurationService: ServerConfigurationService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ServerConfigurationService,
{
provide: ConfigService,
useFactory: mockConfigService
},
{
provide: ValidationServiceImpl,
useClass: ValidationMockService
},
],
}).compile();
serverConfigurationService = module.get<ServerConfigurationService>(ServerConfigurationService);
});
it('should be defined', () => {
expect(serverConfigurationService).toBeDefined();
});
});
现在,当运行测试时,ValidationMockService
将被使用。另外,除了修正测试之外,您还可以将关注点完全分开。
我在这里提供的重构只是一个如何继续的示例。我猜想,取决于您的进一步用例,您可能会削减ValidationService
与我不同的削减,甚至将更多的关注点分离到新的服务类中。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句