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

如何单元测试AngularJS控制器外的注入器逻辑?含$http Mock方案

问题分析与解决方案

你当前测试失败的核心原因是:那段初始化代码是在测试运行前就全局自动执行的,而你在测试用例里设置的spyOn(angular, 'injector')是在module('app')加载后才生效,根本没覆盖到实际执行的初始化逻辑。要解决这个问题,我们需要先重构代码让它可测试,再针对性mock依赖来验证不同场景。


第一步:重构初始化代码,让它可测试

把原本全局执行的初始化逻辑封装成一个可调用的函数,这样我们可以在测试里手动触发它,而非页面加载时自动执行:

// 封装初始化逻辑为可调用函数
window.initApp = function() {
  var initInjector = angular.injector(['ng']);
  var $http = initInjector.get('$http');
  // 注意:原代码里的response.user大概率是笔误,Angular $http返回的服务器数据在response.data里
  $http.get('../api/user').then(
    function (response) {
      if (response.data.user.valid) {
        angular.bootstrap(document, ['app']);
      }
    },
    function () {
      window.location.href = '../userError.html';
    }
  );
};

// 生产环境下,页面加载完成后自动执行初始化
document.addEventListener('DOMContentLoaded', function() {
  window.initApp();
});

第二步:编写单元测试,mock依赖验证场景

现在我们可以在测试里手动调用initApp(),并mockangular.injector和$http的行为,分别测试成功、失败、用户无效三种场景:

describe('App Initialization', function() {
  beforeEach(function() {
    // 重置所有mock和spy
    // 1. mock angular.injector,返回我们控制的$http服务
    spyOn(angular, 'injector').and.returnValue({
      get: function(serviceName) {
        if (serviceName === '$http') {
          // 返回带mock then方法的$http
          return {
            get: jasmine.createSpy('$http.get').and.returnValue({
              then: function(successCb, errorCb) {
                // 保存回调,方便测试时手动触发
                this.successCallback = successCb;
                this.errorCallback = errorCb;
                return this;
              }
            })
          };
        }
      }
    });
    // 2. spy angular.bootstrap,验证是否触发应用启动
    spyOn(angular, 'bootstrap');
    // 3. spy window.location.href,防止测试时真的跳转页面
    spyOn(window.location, 'href').and.callFake(function() {});
  });

  it('should bootstrap the app when valid user is returned', function() {
    // 调用初始化函数
    window.initApp();
    
    // 获取$http.get的返回对象,触发成功回调
    var httpGetSpy = angular.injector().get('$http').get;
    var thenable = httpGetSpy.calls.mostRecent().returnValue;
    thenable.successCallback({ data: { user: { valid: true } } });
    
    // 验证应用启动逻辑被触发
    expect(angular.bootstrap).toHaveBeenCalledWith(document, ['app']);
    // 验证跳转逻辑未触发
    expect(window.location.href).not.toHaveBeenCalled();
  });

  it('should redirect to error page when $http request fails', function() {
    window.initApp();
    
    var httpGetSpy = angular.injector().get('$http').get;
    var thenable = httpGetSpy.calls.mostRecent().returnValue;
    // 触发错误回调
    thenable.errorCallback();
    
    // 验证跳转逻辑被触发
    expect(window.location.href).toHaveBeenCalledWith('../userError.html');
    // 验证应用启动逻辑未触发
    expect(angular.bootstrap).not.toHaveBeenCalled();
  });

  it('should NOT bootstrap the app when user is invalid', function() {
    window.initApp();
    
    var httpGetSpy = angular.injector().get('$http').get;
    var thenable = httpGetSpy.calls.mostRecent().returnValue;
    thenable.successCallback({ data: { user: { valid: false } } });
    
    expect(angular.bootstrap).not.toHaveBeenCalled();
    expect(window.location.href).not.toHaveBeenCalled();
  });
});

关键细节说明

  1. 封装初始化函数:解决了测试时代码提前执行导致spy失效的问题,让我们能完全控制初始化逻辑的执行时机。
  2. mock $http服务:因为这里的$http是从全局angular.injector获取的,而非Angular应用内的依赖,所以需要手动mock它的get和then方法,方便我们手动触发回调。
  3. 避免真实跳转:通过spyOn(window.location.href)并设置空的callFake,防止测试过程中真的跳转到错误页面。
  4. 修正response路径:原代码里的response.user是常见笔误,Angular的$http响应中,服务器返回的数据始终在response.data属性下,生产环境也需要修正这一点才能正常工作。

内容的提问来源于stack exchange,提问作者mccarthyj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:04:24