Que*_*n3r 1 unit-testing node.js jestjs nestjs
我想要进行单元测试,并为我想要测试的 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我可以使用多个配置服务来扩展此服务。为了简单起见,我将为此使用服务器配置服务。目前它只保存应用程序将侦听的端口。
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
);
}
}
Run Code Online (Sandbox Code Playgroud)
我发现了多篇文章,我应该只测试公共方法,例如
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();
});
});
Run Code Online (Sandbox Code Playgroud)
但正如您在第二个代码片段中看到的,我正在构造函数中从基本服务调用函数。测试立即失败
验证错误:“SERVER_PORT”必须是数字
有没有办法可以对配置服务进行单元测试,尽管它们依赖于抽象基类和外部 .env 文件?因为我知道我可以创建一个,mockConfigService但我认为基类打破了这一点。我不知道如何修复这个测试文件。
主要问题归结为:您正在使用 Joi 库来解析环境变量。每当您调用 时validateValue,都会调用 Joi 函数,该函数期望设置实际的环境变量(在本例中为SERVER_PORT)。现在需要设置这些环境变量是运行服务的有效假设。但在您的测试用例中,您没有设置环境变量,因此 Joi 验证失败。
一个原始的解决方案是process.env.SERVER_PORT在 your 中设置某个值beforeEach并在afterEach. 然而,这只是围绕实际问题的解决方法。
实际问题是:您硬编码的库调用BaseConfigurationService假设环境变量已设置。我们之前已经发现,在运行测试时这不是一个有效的假设。当您在编写测试时偶然发现此类问题时,通常表明存在紧耦合问题。
我们该如何解决这个问题?
BaseConfigurationService. 我们称之为服务类ValidationService。BaseConfigurationService。ValidationService因此它不依赖于实际的环境变量,但是,例如,在验证期间不会抱怨任何事情。下面是我们如何一步步实现这一目标:
1.定义一个ValidationService接口
该接口简单地描述了一个类需要如何验证值:
import { AnySchema } from '@hapi/joi';
export interface ValidationService {
validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void;
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
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
);
}
}
Run Code Online (Sandbox Code Playgroud)
6.测试中使用mock服务
最后,现在这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();
});
});
Run Code Online (Sandbox Code Playgroud)
现在运行测试时ValidationMockService将使用。另外,除了修复测试之外,您还可以清楚地分离关注点。
我在这里提供的重构只是一个如何继续进行的示例。我想,根据您的进一步用例,您可能会采取ValidationService与我不同的方式,甚至将更多的关注点分成新的服务类。
| 归档时间: |
|
| 查看次数: |
3503 次 |
| 最近记录: |