Angular服务测试:如何在测试中设置环境变量控制服务逻辑
如何在Angular单元测试中设置环境变量触发分支逻辑
我在Angular服务中实现了getPurchasableRewards方法,该方法依赖environment.sessionmRewardStore和environment.sessionmStoreEligibilityKeys两个环境变量做分支处理。写单元测试时想设置这两个变量触发对应分支,但不确定当前实现是否正确,相关代码如下:
服务代码
public async getPurchasableRewards() { let userStores: RewardStore[] = []; await this.sessionmApi.getUserRewardStores().then((r) => { if (Array.isArray(r)) { // 这里需要设置自定义的environment.sessionmRewardStore值 if (environment.sessionmRewardStore) { userStores = r.filter((s) => { return s.id === environment.sessionmRewardStore; }); } else { userStores = r.filter((s) => { // 这里需要设置自定义的environment.sessionmStoreEligibilityKeys值 return s.eligibility_models.find((e) => environment.sessionmStoreEligibilityKeys.includes(e)); }); } } }); }
测试代码
fit('should getPurchasableRewards return .....', (doneFn) => { const mock = { type: AppStateActions.PURCHASABLE_REWARDS, payload: MockIPurchasableReward }; const spysgetValue = spyOn(service['sessionmApi'], 'getUserRewardStores').and.returnValue(Promise.resolve(MockRewardStoreArray)); // 我想把这个变量传给服务: environment.sessionmRewardStore='STORE-1' service.getPurchasableRewards().then((data) => { console.log('DATA PP:', data) doneFn(); }) .catch((err) =>{ console.log('error', err) }); });
解决方案与问题分析
一、当前代码的核心问题
- 全局环境变量污染:直接修改
environment对象会影响其他测试用例,因为它是全局单例,测试结束后不会自动重置。 - 方法无返回值:
getPurchasableRewards没有返回处理后的userStores,导致测试中data始终为undefined,无法断言结果是否符合预期。 - 异步写法不规范:混用
async/await和.then,既降低可读性,也容易引发测试中的异步时序问题。
二、正确设置环境变量的两种方式
方式1:测试前修改+测试后重置(简单场景适用)
在单个测试用例中临时修改环境变量,测试完成后重置为原始值,避免污染全局状态:
describe('YourService', () => { let originalRewardStore: string | undefined; let originalEligibilityKeys: string[] | undefined; let service: YourService; beforeEach(() => { TestBed.configureTestingModule({ providers: [YourService] }); service = TestBed.inject(YourService); // 保存环境变量原始值 originalRewardStore = environment.sessionmRewardStore; originalEligibilityKeys = environment.sessionmStoreEligibilityKeys; }); afterEach(() => { // 测试结束后重置回原始值 environment.sessionmRewardStore = originalRewardStore; environment.sessionmStoreEligibilityKeys = originalEligibilityKeys; }); fit('should filter rewards by sessionmRewardStore', (doneFn) => { // 设置测试用环境变量 environment.sessionmRewardStore = 'STORE-1'; // 模拟返回包含目标商店的数据集 const mockStores = [{ id: 'STORE-1', eligibility_models: [] }, { id: 'STORE-2', eligibility_models: [] }]; spyOn(service['sessionmApi'], 'getUserRewardStores').and.returnValue(Promise.resolve(mockStores)); // 先修改服务方法,让它返回userStores,才能进行断言 service.getPurchasableRewards().then(data => { expect(data.length).toBe(1); expect(data[0].id).toBe('STORE-1'); doneFn(); }).catch(err => doneFn.fail(err)); }); fit('should filter rewards by sessionmStoreEligibilityKeys', (doneFn) => { // 清空store值触发else分支 environment.sessionmRewardStore = undefined; // 设置测试用的资格密钥 environment.sessionmStoreEligibilityKeys = ['ELIGIBLE-KEY']; // 模拟返回包含匹配资格模型的数据集 const mockStores = [ { id: 'STORE-1', eligibility_models: ['ELIGIBLE-KEY'] }, { id: 'STORE-2', eligibility_models: ['OTHER-KEY'] } ]; spyOn(service['sessionmApi'], 'getUserRewardStores').and.returnValue(Promise.resolve(mockStores)); service.getPurchasableRewards().then(data => { expect(data.length).toBe(1); expect(data[0].id).toBe('STORE-1'); doneFn(); }).catch(err => doneFn.fail(err)); }); });
方式2:依赖注入环境配置(更优雅的可测试方案)
直接依赖全局environment不利于解耦,建议封装成可注入的配置服务,测试时直接模拟该服务:
- 创建环境配置服务
import { Injectable } from '@angular/core'; import { environment } from '../environments/environment'; @Injectable({ providedIn: 'root' }) export class EnvConfigService { get sessionmRewardStore() { return environment.sessionmRewardStore; } get sessionmStoreEligibilityKeys() { return environment.sessionmStoreEligibilityKeys; } }
- 修改业务服务,注入配置服务
import { EnvConfigService } from './env-config.service'; // ... constructor(private sessionmApi: SessionmApi, private envConfig: EnvConfigService) {} public async getPurchasableRewards() { const r = await this.sessionmApi.getUserRewardStores(); if (!Array.isArray(r)) return []; if (this.envConfig.sessionmRewardStore) { return r.filter(s => s.id === this.envConfig.sessionmRewardStore); } else { return r.filter(s => s.eligibility_models.some(e => this.envConfig.sessionmStoreEligibilityKeys.includes(e)) ); } }
- 测试时模拟配置服务
fit('should filter by reward store', (doneFn) => { // 创建模拟配置服务,自定义返回值 const mockEnvConfig = jasmine.createSpyObj('EnvConfigService', [], { sessionmRewardStore: 'STORE-1', sessionmStoreEligibilityKeys: [] }); // 替换业务服务中的配置服务依赖 const service = TestBed.inject(YourService); service['envConfig'] = mockEnvConfig; const mockStores = [{ id: 'STORE-1' }, { id: 'STORE-2' }]; spyOn(service['sessionmApi'], 'getUserRewardStores').and.returnValue(Promise.resolve(mockStores)); service.getPurchasableRewards().then(data => { expect(data.length).toBe(1); doneFn(); }).catch(err => doneFn.fail(err)); });
三、额外代码优化建议
- 统一用
await处理异步,去掉async/await和.then的混用,提升代码可读性; - 用
Array.some()替代find(),语义更明确(判断是否存在匹配项而非返回匹配项)。
内容的提问来源于stack exchange,提问作者Marcos Galaviz
相关产品推荐
相关产品推荐

