如何使用angular-translate进行单元测试

40

我使用了来自这里的angular translate (http://pascalprecht.github.io/angular-translate/),它运行良好,但它却破坏了我的控制器单元测试并出现错误:

Unexpected request: GET scripts/i18n/locale-en.json

我不明白为什么?

我使用yeoman和karma进行测试。

app.js:

'use strict';

(function() {

  angular.module('wbApp', ['authService', 'authUserService', 'checkUserDirective', 'ui.bootstrap', 'pascalprecht.translate'])
    .config(function($routeProvider) {
      $routeProvider
        .when('/', {
          templateUrl: 'views/login.html',
          controller: 'LoginCtrl',
          access: {
            isFree: true
          }
        })
        .when('/main', {
          templateUrl: 'views/main.html',
          controller: 'MainCtrl',
          access: {
            isFree: false
          }
        })
        .otherwise({
          redirectTo: '/'
        });
    });

})();

configTranslate.js:

'use strict';

(function() {

  angular.module('wbApp')
    .config(['$translateProvider',
      function($translateProvider) {

        $translateProvider.useStaticFilesLoader({
            prefix: 'scripts/i18n/locale-',
            suffix: '.json'
        });

        $translateProvider.preferredLanguage('en');

      }]);

})();

karma.conf.js:

files = [

  ...

  'app/bower_components/angular-translate/angular-translate.js',
  'app/bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',

  ...

];

控制器测试:

'use strict';

describe('Controller: LoginCtrl', function() {

  // load the controller's module
  beforeEach(module('wbApp'));

  var LoginCtrl, scope, location, httpMock, authUser;

  // Initialize the controller and a mock scope
  beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, AuthUser) {
    authUser = AuthUser;
    location = $location;
    httpMock = $httpBackend;
    scope = $rootScope.$new();

    LoginCtrl = $controller('LoginCtrl', {
      $scope: scope
    });


    httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();

  }));

  it(...);

  ...

});

如果我在测试控制器中添加这个,产品仍然会出现相同的错误:

httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(200);
httpMock.flush();

或者

httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.flush();

我发现这篇文章《如何测试在应用程序配置中初始化了Angular Translate的控制器?》,但没有帮助到我 :/
我在我的测试中广泛使用$httpBackend,它工作得很好,但在这种情况下却无效。如果我注释掉以下行:
$translateProvider.preferredLanguage('en');

很明显是个错误,如果我在运行时(在我的控制器中)添加的话。
$translate.uses(local);

我最终遇到了相同的错误?

所以我转向翻译配置(configTranslate.js),或在运行时得到相同的结果:

Unexpected request: GET scripts/i18n/locale-en.json

以下是我测试过的语法,可以在“beforeEach(inject(function(...)}”中使用,也可以在测试“it('...', function() {...});”中使用。

httpMock.expectGET('scripts/i18n/locale-en.json');
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(data);

在结尾处使用“at”

httpMock.flush();

我也尝试了一个 $ apply。
httpMock.expectGET('scripts/i18n/locale-fr.json');
scope.$apply(function(){
  $translate.uses('fr');
});
httpMock.flush();

什么也没有发生,但这个错误让我疯狂......

如果您有任何建议:


错误信息显示它收到了 scripts/i18n/locale-en.json. 注意末尾的额外句点?这是一个打字错误还是真实的信息?因为您期望不带句点结尾的请求,所以它可能是问题的原因。 - jusio
谢谢回答,但是这是我的错,末尾的点只是一个打字错误...已经更正。 - bin
我有同样的问题。看起来$translateProvider在调用http获取json文件时,会在应用程序模块的config方法被调用时进行。由于这是在应用程序设置期间进行的,因此似乎尝试插入http存根是一个先有鸡还是先有蛋的问题。我目前正在研究自己的解决方案。 - Marplesoft
有人找到解决方案了吗? - Snæbjørn
请查看我在这里的答案:https://dev59.com/N2Ik5IYBdhLWcg3wbdrx#20082591,了解我使用的解决方案。 - urish
11个回答

28

这是一个已知问题,请遵循此处的文档:unit testing angular

解决方案

不幸的是,这个问题是由 angular-translate 的设计引起的。为了避免这些错误,我们可以在测试套件中覆盖模块配置,使其根本不使用异步加载。当没有异步加载时,就没有 XHR,因此也没有错误。

那么,在运行时如何覆盖我们的测试套件的模块配置呢?在实例化一个 Angular 模块时,我们总是可以应用一个内联函数作为配置函数执行。由于我们可以访问所有提供程序,因此可以使用此配置函数来覆盖模块配置。

使用 $provide 提供程序,我们可以构建一个自定义的加载器工厂,然后应该使用它代替静态文件加载器。

beforeEach(module('myApp', function ($provide, $translateProvider) {

  $provide.factory('customLoader', function () {
    // loader logic goes here
  });

  $translateProvider.useLoader('customLoader');

}));

请点击上面提供的链接以获取更多信息。


那个解决方案真的不可接受。我仍然需要为每个测试声明它。关于这个问题是否有未解决的问题? - hugo der hungrige
我同意Hugo的看法,测试使用可调用的Angular服务的代码通常(不仅仅是$translate,还包括$timeout和$interval)相当麻烦。每个解决方案都需要一些特殊技巧(例如$timeout.flush())才能进行半路测试。就我所知(我只是一个初学者,但这是我目前的看法),所有这些服务的测试都严重依赖于这些服务正确地行为。如果任何一个原因导致其中一个服务出现问题,我的测试用例将会失效,即使它们应该正常工作来测试的代码也是正确的。 - everclear

15

我们采取的方法是在单元测试中忽略翻译加载器,而不是被迫修改每个规范文件。

一种方法是将加载器配置分离到单独的文件中,然后在karma中排除它。

所以例如,您可以创建一个名为app-i18n-loader.js的文件(所有其他模块配置在不同的文件中进行):

    angular
    .module('myApp')
    .config(loaderConfig);

loaderConfig.$inject = ['$translateProvider', '$translatePartialLoaderProvider'];

function loaderConfig($translateProvider, $translatePartialLoaderProvider) {

    $translateProvider.useLoader('$translatePartialLoader', {
        urlTemplate: 'assets/i18n/{part}/{lang}.json'
    });

    $translatePartialLoaderProvider.addPart('myApp');
}

并且在你的 karma.conf.js 文件中排除这个文件:

        files: [
        'bower_components/angular/angular.js',
        'bower_components/angular-mocks/angular-mocks.js',
        //...
        'bower_components/angular-translate/angular-translate.js',
        'bower_components/angular-translate-loader-partial/angular-translate-loader-partial.js',
        'app/**/*.mdl.js',
        'app/**/*.js'
    ],

    exclude: [
        'app/app-i18n-loader.js'
    ],

(注:答案已编辑为不需要 grunt/gulp 的解决方案)。


13

我需要一个解决方案,

  1. 不要太过hacky
  2. 不需要我改变我的实际应用程序代码,
  3. 不会干扰加载其他模块的能力
  4. 最重要的是,不需要我更改每个单独的测试。

这就是我最终得出的结果:

// you need to load the 3rd party module first
beforeEach(module('pascalprecht.translate'));
// overwrite useStaticFilesLoader to get rid of request to translation file
beforeEach(module(function ($translateProvider) {
    $translateProvider.useStaticFilesLoader = function () {
    };
}));

假设您不需要实际翻译用于单元测试,这很好用。只需将beforeEach放在全局级别上,最好是在测试文件夹内的自己的文件中。然后它将在每个其他测试之前执行。


适用于测试翻译服务的使用情况,但不适用于翻译本身。在相关测试中(测试http拦截器触发正确的错误消息警报和默认通用消息),我最终调用了$translateProvider.translations('en'.... - Tiago Roldão
$translateProvider.useStaticFilesLoader = function () { return $translateProvider; //用于链接的返回 }; - kongaraju

4
我在使用Protractor测试时遇到了这个问题。我的解决方案是像这样模拟翻译:
```javascript

我在使用Protractor测试时遇到了这个问题。我的解决方案是像这样模拟翻译:

```
angular.module('app')
        .config(function ($translateProvider) {
            $translateProvider.translations('en', {});
            $translateProvider.preferredLanguage('en');
        })

现在没有下载任何语言文件,也没有翻译任何字符串,我只是根据规范中的字符串键进行测试:

expect(element(by.css('#title')).getText()).toEqual('TITLE_TEXT');

3
尝试使用“进行测试”方法:

尝试使用“进行测试”方法:

it('should ...', function() {
    httpMock.when('GET', 'scripts/i18n/locale-en.json').respond({});
    httpMock.expectGET('scripts/i18n/locale-en.json');
    scope.resetForm(); // Action which fires a http request
    httpMock.flush(); // Flush must be called after the http request
}

请参考Angular文档中的示例。


这就是我所做的,但没有任何改变?之后我也尝试了 httpMock.flush(); - bin
你能把你的完整测试代码粘贴到http://jsfiddle.net/吗?我可以看看是否能提供帮助。 - Matti Lehtinen
当然,你可以在这里找到我的loginCtrl代码: http://jsfiddle.net/s88EX/ - bin
不幸的是,它也不起作用。难以置信,我是唯一一个遇到这个“错误”的人吗?我感觉是这个命令: translateProvider.preferredLanguage $ ('en'); xhr在测试实例化之前就引起了这个问题,并且无法通过 httpMock.when (...) or httpMock.expectGET (...)进行拦截。 - bin
如果你在 beforeEach 中重写 $translateProvider.preferredLanguage 函数,这样做有帮助吗?当然,你需要注入 $translateProvider - Matti Lehtinen
这种方法的问题是,你拦截消息并阻止http调用所进行的任何黑客攻击都仅局限于一个位置。如果您想注释掉该测试并在某个过滤器上运行测试或不同的控制器上运行测试怎么办?没有那些mocking东西,一旦您尝试将任何东西从应用程序模块注入到任何地方,它就会运行module.config并进行http调用并且崩溃。 - Marplesoft

3

1
我为$translate制作了一个简单的模拟服务。
$translate=function (translation) {
    return {
      then: function (callback) {
        var translated={};
        translation.map(function (transl) {
          translated[transl]=transl;
        });
        return callback(translated);
      }
    }
  };

这里是使用示例: https://gist.github.com/dam1/5858bdcabb89effca457


1

对我来说,没有任何解决方案可用,但我提出了以下解决方案:

1)如果需要使用scope.$apply(),或者应该在测试中处理状态(在$apply()之后第二种方法将无法工作),请使用$translateProvider.translations()方法覆盖您的应用程序的翻译,并使用插件加载JSON文件

beforeEach(module(function ($translateProvider) {
    $translateProvider.translations('en', readJSON('scripts/i18n/locale-en.json'));
}));

2) 如果你的测试控制器依赖于$translate服务,你可以使用一个插件来加载JSON文件,并结合$httpBackend在angular-translate请求时加载你的本地化文件。

beforeEach(inject(function (_$httpBackend_) {
    $httpBackend = _$httpBackend_;

    $httpBackend.whenGET('scripts/i18n/locale-en.json').respond(readJSON('scripts/i18n/locale-en.json'));
    $httpBackend.flush();
})));

请注意,这应该在beforeEach(module('myApp'));之后,否则您将会收到一个$injector错误。

0
有点晚了,但我通过在 karma.conf.js 中指定 Karma 只需按照此条目提供文件来解决了这个问题。
files: [
    ...
    {pattern: 'scripts/i18n/*.json', included: false, served: true},
    ...
]

0

我使用这个模式。

  • ApplicationModule设置常规的angular-translate配置。
  • 测试代码加载“testModule”而不是“applicationModule”

// application module .js 
(function() {
  'use strict'; 
  
  angular
   .module('applicationModule', [
    'ngAnimate',
    'ngResource',
    'ui.router',
    'pascalprecht.translate'
  ])
  .config(['$stateProvider', '$urlRouterProvider', '$translateProvider', '$translatePartialLoaderProvider', config]);

  function config($stateProvider, $urlRouterProvider, $translateProvider, $translatePartialLoaderProvider) {
    // set routing ... 
        
    $translateProvider.useStaticFilesLoader({
      prefix: 'i18n/locale-',
      suffix: '.json'
    });

    $translateProvider.useMessageFormatInterpolation();
    $translateProvider.fallbackLanguage(['en']);
    $translateProvider
    .registerAvailableLanguageKeys(['en', 'ko'], {
      'en_US': 'en',
      'ko_KR': 'ko'
    })
    .determinePreferredLanguage(navigator.browserLanguage);

            
    $translateProvider.addInterpolation('$translateMessageFormatInterpolation');    
    $translateProvider.useSanitizeValueStrategy('escaped');
  }

})();

// test.module.js
(function() {
  'use strict';

  angular
    .module('testModule', ['applicationModule'])
    .config(['$translateProvider', '$translatePartialLoaderProvider', config])
    .run(['$httpBackend', run]);

  function config($translateProvider, $translatePartialLoaderProvider) {
    $translateProvider.useLoader('$translatePartialLoader', {
        urlTemplate: 'i18n/locale-en.json'
    });
    $translatePartialLoaderProvider.addPart('applicationModule');
  }

  function run($httpBackend) {
    $httpBackend.when('GET', 'i18n/locale-en.json').respond(200);
  }

})();


// someDirective.spec.js
describe("a3Dashboard", function() {
    beforeEach(module("testModule"))

    var element, $scope;
    beforeEach(inject(function($compile, $rootScope) {
        $scope = $rootScope;
        element = angular.element("<div>{{2 + 2}}</div>");
        $compile(element)($rootScope)
    }))

    it('should equal 4', function() {
      $scope.$digest();
      expect(element.html()).toBe("4");
    })

})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接