40

我从这里(http://pascalprecht.github.io/angular-translate/)使用了角度翻译,它工作正常,但它破坏了我的控制器的单元测试,并出现错误:

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

我不明白为什么?

我使用 yeoman 并使用 karma 进行测试。

应用程序.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');

      }]);

})();

业力.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();

我发现这篇文章如何使用在 App Config 中初始化的 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);

最后

httpMock.flush();

我也尝试了 $ apply

httpMock.expectGET('scripts/i18n/locale-fr.json');
scope.$apply(function(){
  $translate.uses('fr');
});
httpMock.flush();

什么也没发生,这个错误仍然让我发疯..

如果您有任何建议

4

11 回答 11

28

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

解决方案

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

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

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

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

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

  $translateProvider.useLoader('customLoader');

}));

请在上面提供的链接中阅读更多信息。

于 2014-11-28T17:39:12.103 回答
15

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

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

因此,例如,您可以创建一个文件 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 的解决方案)。

于 2015-04-06T15:19:19.907 回答
12

我想要一个解决方案,

  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 放在全局级别,最好放在 test 文件夹内它自己的文件中。它将在所有其他测试之前执行。

于 2015-12-15T19:47:54.390 回答
4

我在量角器测试中遇到了这个问题。我的解决方案是模拟这样的翻译:

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

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

expect(element(by.css('#title')).getText()).toEqual('TITLE_TEXT');
于 2015-09-25T08:53:43.970 回答
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 文档中的示例

于 2013-09-19T09:37:21.207 回答
3

请查看https://github.com/PascalPrecht/angular-translate/blob/master/test/unit/service/loader-static-files.spec.js作为参考。

一般来说,我建议使用标准的翻译加载器进行单元测试(没有 http 加载的麻烦),这意味着您可以提供带有$translateProvider.translations(). 为什么?因为您不必测试作为 angular-translate 项目一部分的远程加载功能。

于 2013-12-15T15:48:50.927 回答
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错误消息。

于 2015-09-05T04:27:30.780 回答
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

于 2016-01-20T16:47:05.650 回答
0

我使用这种模式。

  • ApplicationModule 设置常规角度翻译配置。
  • 测试代码加载“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");
    })

})
于 2015-04-20T05:58:24.627 回答
0

2016 年的答案是将您的 json 预处理到您的测试中,并在您的指令上正确测试翻译工作。

我使用 karma-ng-json2js-preprocessor。按照所有步骤设置您的 karma.conf,然后在您的测试文件中,将相关文件作为模块添加,然后在 $translateProvider 中设置该信息。

beforeEach(module('myApp', '/l10n/english-translation.json'));

// Mock translations for this template
beforeEach(module(function($translateProvider, englishTranslation) {
    $translateProvider.translations('en_us', englishTranslation);
    $translateProvider.useSanitizeValueStrategy(null);
    $translateProvider.preferredLanguage('en_us');
}));

请注意,根据插件,它使用您的文件名生成驼峰式模块名称。您可以在模块的 /lib 中使用该函数,但基本上它会删除所有破折号,但 KEEPS 下划线以驼峰形式出现。所以 en_us 变成了 En_us。

您还需要告诉您的测试它期望该文件作为一个 GEt。

    $httpBackend.expect('GET', '/l10n/english-translation.json').respond(200);
于 2016-11-14T02:50:26.270 回答
0

迟到了,但我通过指定 Karma 只是根据以下条目提供文件来解决这个问题karma.conf.js

files: [
    ...
    {pattern: 'scripts/i18n/*.json', included: false, served: true},
    ...
]
于 2016-04-11T20:08:15.353 回答