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

时间:2013-09-18 15:23:41

标签: unit-testing angularjs mocking yeoman karma-runner

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

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

我不明白为什么?

我使用自耕农并用业力进行测试。

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

我发现这篇文章How do I test controllers with Angular Translate initialized in App Config?但没有帮助我:/

我在我的测试中广泛使用$ 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();

没有任何反应,但这个错误让我发疯了......

如果您有任何建议

11 个答案:

答案 0 :(得分:28)

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

  

解决方案

     

不幸的是,这个问题是由设计引起的   角翻译。为了解决这些错误,我们所能做的就是   在我们的测试套件中覆盖我们的模块配置,它不会   完全使用异步加载器。当没有异步加载器时   没有XHR,因此没有错误。

     

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

     

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

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

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

  $translateProvider.useLoader('customLoader');

}));

请在上面提供的链接中阅读更多内容。

答案 1 :(得分:15)

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

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

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

答案 2 :(得分:12)

我想要一个解决方案,

  1. 哪个不太hacky
  2. 并不要求我更改我的实际应用程序代码,
  3. 不会干扰加载其他模块的能力
  4. 最重要的是,我不需要改变每一个 单一测试。
  5. 这就是我最终的结果:

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

答案 3 :(得分: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 docs

中的示例

答案 4 :(得分:3)

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

一般情况下,我建议使用标准的翻译加载程序进行单元测试(没有http加载的麻烦),这意味着您可以使用$translateProvider.translations()提供标签。为什么?因为您不必测试角度转换项目中的远程加载功能。

答案 5 :(得分:3)

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

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

现在没有下载语言文件,没有字符串被翻译,我只是在规范中测试字符串键:

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

答案 6 :(得分:1)

没有一个解决方案适合我,但我带来了这些解决方案:

1)如果您需要使用scope.$apply(),或者应该在测试中处理状态(在$apply()第二种方法失败后),请覆盖您的应用使用load JSON files

的插件使用$translateProvider.translations()方法进行翻译
beforeEach(module(function ($translateProvider) {
    $translateProvider.translations('en', readJSON('scripts/i18n/locale-en.json'));
}));

2)如果您的测试控制器依赖于$translate服务,您可以使用插件load JSON files并将其与$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错误。

答案 7 :(得分: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

答案 8 :(得分: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");
    })

})

答案 9 :(得分:0)

在这篇文章的后面,我通过指定Karma根据karma.conf.js中的此条目仅提供文件来解决这个问题:

files: [
    ...
    {pattern: 'scripts/i18n/*.json', included: false, served: true},
    ...
]

答案 10 :(得分: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');
}));

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

您还需要告诉您的测试,它希望该文件为GEt。

    $httpBackend.expect('GET', '/l10n/english-translation.json').respond(200);