gpt4 book ai didi

node.js - 如何对扩展抽象类读取环境变量的类进行单元测试

转载 作者:行者123 更新时间:2023-12-04 01:29:39 29 4
gpt4 key购买 nike

我想进行单元测试,并为我想要测试的 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
);
}
}

我在那里发现了多篇文章,我应该只测试公共(public)方法,例如

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" must be a number



有没有办法可以对配置服务进行单元测试,尽管它们依赖于抽象基类和外部 .env 文件?因为我知道我可以创建 mockConfigService但我认为基类打破了这一点。我不知道如何修复这个测试文件。

最佳答案

主要问题归结为:您正在使用 Joi 库来解析环境变量。无论何时调用 validateValue , 调用 Joi 函数,期望设置实际环境变量(在本例中为 SERVER_PORT )。现在需要设置这些环境变量是运行服务的有效假设。但是在您的测试用例中,您没有设置环境变量,因此 Joi 验证失败。

一个原始的解决方案是设置 process.env.SERVER_PORT在您的beforeEach 中获得一些值(value)并在 afterEach 中删除.但是,这只是解决实际问题的方法。

实际问题是:您将库调用硬编码到 BaseConfigurationService 中。假设设置了环境变量。我们之前已经发现,在运行测试时,这不是一个有效的假设。当您在编写测试时偶然发现此类问题时,通常会指出紧密耦合的问题。

我们如何解决这个问题?

  • 我们可以清楚地分离关注点并将实际验证抽象为BaseConfigurationService 使用的自己的服务类。 .我们称该服务类为 ValidationService .
  • 然后我们可以将该服务类注入(inject) BaseConfigurationService使用 Nest 的依赖注入(inject)。
  • 运行测试时,我们可以模拟 ValidationService所以它不依赖于实际的环境变量,但是,例如,在验证期间不会提示任何事情。

  • 因此,我们可以逐步实现这一目标:

    1.定义一个ValidationService接口(interface)

    该接口(interface)简单地描述了一个可以验证值的类的外观:
    import { AnySchema } from '@hapi/joi';

    export interface ValidationService {
    validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void;
    }

    2. 实现验证服务

    现在我们将从您的 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注入(inject)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注入(inject)并传递给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.在测试中使用mock服务

    最后,现在 ValidationServiceImplBaseConfigurationService 的依赖项,我们在测试中使用模拟版本:
    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与我所做的不同,甚至将更多关注点分离到新的服务类中。

    关于node.js - 如何对扩展抽象类读取环境变量的类进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61286624/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com