【问题标题】:angular unit test state change with resolve角度单元测试状态随解析而变化
【发布时间】:2026-02-21 22:40:01
【问题描述】:

场景

我已经构建了具有 2 个状态的搜索表单: 第一个状态触发第二个(onclick),并使用 $state.go() 函数设置一些状态参数。 第二个状态有解析函数,获取状态参数,解析搜索结果。

这种方法看起来更简洁,与经典方法相反:在状态一中注入搜索服务,获取结果,将它们保存在搜索服务中,更改状态并从注入的服务加载结果.

问题

如何为此进行单元测试?

最初的想法是我应该做一个“应该用 searchTerm 触发定位并切换到结果页面”的测试。 它失败了,因为状态没有正确解决茉莉花。 有时它在第一个状态下“锁定”,在其他情况下状态始终是“”。

当我从配置中删除解析块时,它按预期工作,并且测试通过,所以问题显然出在解析块中。奇怪的是,我没有收到“未知提供者”错误。

任何想法或想法如何解决这个问题?

关于 * 的最接近的问题是: Angular ui router unit testing (states to urls) 但是用户手动调用状态更改,而在我的场景中它是由另一个函数调用的。

单元测试代码:

describe('SearchController tests', function(){
    var ctrl, $state, $rootScope, $httpBackend;

    // instantiate the app to provide all the dependencies
    beforeEach(module('MyApp'));

    beforeEach(inject(function($controller, _$state_, _$rootScope_, _$httpBackend_) {
        ctrl = $controller('SearchController', {});
        $state = _$state_;
        $httpBackend = _$httpBackend_;
        $rootScope = _$rootScope_;

        // mocking the views 
        // (instead of including stateMock script from the post, just to simplify)
        $httpBackend.expectGET("search.html").respond("<input ng-model='vm.searchTerm'><button ng-click='vm.search()'>Search</button>");
        $httpBackend.expectGET("search-results.html").respond("<div ng-repeat='res in vm.results'> {{ res }}</div>");

        $state.go('search');
        $rootScope.$digest();
    }));

    it('should trigger a search with searchTerm "rabbit" and change state to search results page', function(){
        ctrl.searchTerm = 'rabbit';
        $state.go('search');
        ctrl.search();
        $rootScope.$digest();
        expect($state.current.name).toBe('search-results');
    })
});

Plunker

http://plnkr.co/edit/6qiYCvCu1LftLzQRyTJy?p=preview

【问题讨论】:

    标签: angularjs unit-testing angular-ui-router


    【解决方案1】:

    这个答案是我在问题中的 note 的另一个变体:

    这种方法似乎更干净,与经典相反 方法:在状态一中注入搜索服务,获取结果,保存 它们在搜索服务中,更改状态并加载注入的结果 服务。

    这意味着,当状态更改为“search-results”时,注入的“search_results”将是一个仍需要在第二个状态下解析的承诺,这意味着 SearchApi 尚未被调用和解析。

    正如我所说,我更喜欢在 状态之间解析承诺,因为它看起来更简洁,并且配置块包含您需要的所有信息,以提高代码的可读性。

    单元测试问题解决方案:

    我解决了我的主要问题,即单元测试。这个问题很常见,我没有包含包含父状态定义的模块。在 plunker 演示中,出于演示的目的,我将所有依赖项放在一个模块中,所以我在单元测试中所要做的就是包含整个模块:

    beforeEach(module('MyApp'));
    

    真正的应用程序要大得多,并且分为多个模块、组件和状态,遵循AngularAtom—Component-based organization

    这种方法的好处之一是,引用:

    每个状态可能包含一个或多个子状态,与子组件不同,每个子状态都定义了自己的模块。这是因为每个 国家,无论是父母还是孩子,都被视为独立公民 可以在应用程序的任何时间添加或删除 生命周期。

    这种特殊的使每个状态成为模块方法的缺点是它使单元测试在嵌套状态下变得更加复杂。

    假设我们有三重嵌套状态(这种情况并不常见):

    • 状态1
      • app.js
      • config.js
      • state1.first
        • app.js
        • config.js ...
        • state1.first.list
          • app.js
          • config.js
          • list-controller.js
          • 列表视图.html ...
        • state1.first.detail ...
      • state1.second ...

    您想对位于state1.first.list 状态下的list-controller.js 进行单元测试。要从我的第一篇文章创建单元测试(在state1.first.list 中测试一个函数,它将状态更改为state1.first.detail 状态),您的测试必须具有以下内容的导入:state1state1.firststate1.first.detail

    beforeEach(module('state1'));
    beforeEach(module('state1.first'));
    beforeEach(module('state1.first.detail'));
    

    原因是:每个模块在 config.js 中都包含它自己的状态配置。如果您尝试执行此测试,仅导入state1.first.liststate1.first.detail,它将失败且没有错误消息。 原因是它缺少在不同模块中定义的父状态配置。 Jasmine 与 PhantomJS 结合默认不会记录此类错误,因此此类错误更难发现,但这是一个不同的问题。

    这些导入使单元测试更紧密地耦合在一起,这似乎违反了这种模式,但这也是一个不同的问题。

    【讨论】:

    • 我认为这超出了集成测试的界限。