How do unit test with angular-translate

2019-01-17 02:49发布

I have uses angular translate from here (http://pascalprecht.github.io/angular-translate/) and it's just work fine, but it break my controller's unit test whith Error:

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

I don't understant why?

I use yeoman and test with 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',

  ...

];

controller test:

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

  ...

});

if i add this in test controller, product same error:

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

or

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

i find this post How do I test controllers with Angular Translate initialized in App Config? but not helped me :/

I extensively use $httpBackend in my tests and it works fine, but in this case it is ineffective. If I comment the line:

$translateProvider.preferredLanguage('en');

obviously an error, if I add on the runtime (in my controllers)

$translate.uses(local);

I end up with the same error?

So I turn to the translation configuration (configTranslate.js) or at runtime is the same result:

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

Here is the syntax that I tested, either in a "beforeEach(inject(function(...});"

or in a test "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);

with at end

httpMock.flush();

I also tried a $ apply

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

nothing happens, Still this error is driving me crazy ..

If you have any suggestion

11条回答
霸刀☆藐视天下
2楼-- · 2019-01-17 03:09

The 2016 answer for this is to preprocess your json into your tests and properly test translations work on your directives.

I use karma-ng-json2js-preprocessor. Follow all the steps to setup your karma.conf then in your test file, prepend the relevant file as a module, then set that information in $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');
}));

Note according to the plugin, it uses your filename to generate a camelcased module name. You can play with the function inside the module's /lib but basically it remove all dashes but KEEPS underscores in a camelCase. So en_us becomes En_us.

You'll also need to tell your test that it is expecting that file as a GEt.

    $httpBackend.expect('GET', '/l10n/english-translation.json').respond(200);
查看更多
萌系小妹纸
3楼-- · 2019-01-17 03:12

I made a simple mock service for $translate

$translate=function (translation) {
    return {
      then: function (callback) {
        var translated={};
        translation.map(function (transl) {
          translated[transl]=transl;
        });
        return callback(translated);
      }
    }
  };

Usage example here : https://gist.github.com/dam1/5858bdcabb89effca457

查看更多
疯言疯语
4楼-- · 2019-01-17 03:14

None of the solutions worked for me but I came with these solutions:

1) If you need to use scope.$apply(), or should deal with states in your test (after the $apply() the 2nd approach won't work), override your app's translations with the $translateProvider.translations() method, using a plugin to load JSON files

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

2) If your tested controller depends on the $translate service you can use a plugin to load JSON files and combine it with $httpBackend to load your locale file when angular-translate requests it.

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

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

Note this should be below your beforeEach(module('myApp')); or you will get an $injector error.

查看更多
Juvenile、少年°
5楼-- · 2019-01-17 03:17

it's a known issue, please follow the documentation here: unit testing angular

The solution

Unfortunately, this issue is caused by the design of angular-translate. To get around these errors, all we can do is to overwrite our module configuration in our test suite, that it doesn't use asynchronous loader at all. When there's no asynchronous loader, there's no XHR and therefore no error.

So how do we overwrite our module configuration at runtime for our test suite? When instantiating an angular module, we can always apply a inline function which is executed as configuration function. This configuration function can be used to overwrite the modules configuration since we have access to all providers.

Using the $provide provider, we can build a custom loader factory, which should then be used instead of the static files loader.

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

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

  $translateProvider.useLoader('customLoader');

}));

Please read more in the above link provided.

查看更多
太酷不给撩
6楼-- · 2019-01-17 03:22

Please have a look at https://github.com/PascalPrecht/angular-translate/blob/master/test/unit/service/loader-static-files.spec.js as a reference.

In general, I would recommend using a standard translation loader for unit tests (without the hassle of http loadings) which means you can provide the labels with $translateProvider.translations(). Why? Because you do not have to test the remote loading functionality which is part of angular-translate project.

查看更多
放我归山
7楼-- · 2019-01-17 03:23

I encountered this problem with protractor tests. My solution was to mock translations like this:

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

Now no language files are downloaded, no strings get translated and I just test against the string keys in specifications:

expect(element(by.css('#title')).getText()).toEqual('TITLE_TEXT');
查看更多
登录 后发表回答