You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
  });
});

解决方案与问题分析

一、当前代码的核心问题

  1. 全局环境变量污染:直接修改environment对象会影响其他测试用例,因为它是全局单例,测试结束后不会自动重置。
  2. 方法无返回值:getPurchasableRewards没有返回处理后的userStores,导致测试中data始终为undefined,无法断言结果是否符合预期。
  3. 异步写法不规范:混用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不利于解耦,建议封装成可注入的配置服务,测试时直接模拟该服务:

  1. 创建环境配置服务
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;
  }
}
  1. 修改业务服务,注入配置服务
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))
    );
  }
}
  1. 测试时模拟配置服务
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 08:10:23