How test ngTranslate #42
Comments
Hey @firehist where does this error appear? In browser console, or is it an http server which is served by ng-boilerplate and the error appears in your terminal? |
@firehist you should provide more details about your test suite/code because the error message already told you: your test case does not work as expected. Either you setup is not correct (the requests should be expected) or your case is not correct (the request should not be fired). Personally, I would guess first option. |
@PascalPrecht and @knalli Thank you for your try to answer even if it's not necessar directly related to ngTranslate.
FROM // Some code
.config( function myAppConfig ( $routeProvider ) {
$routeProvider.otherwise({ redirectTo: '/home' });
})
// Some code TO // Some code
.config( function myAppConfig ( $routeProvider ) {
$routeProvider.otherwise({ redirectTo: '/home' });
/** Start edit **/
$translateProvider.registerLoader({
type: 'static-files',
prefix: 'i18n/',
suffix: '.json'
});
$translateProvider.uses('fr_FR');
/** End edit **/
})
// Some code
My question is how to test that the translateProvider try to catch the right url/file on the server. I tried a lot of different configuration inspired examples of different angular docs: http://docs.angularjs.org/api/ngMock.$httpBackend I hope that all this informations can help you to helping me :D Have a good day and again thanks for all! |
@knalli Thanks. I already took a look to this file but the error "Error: Unexpected request: GET i18n/fr_FR.json" was still there. |
The links points to our test setup where httpBackend mock is instrumented correctly. |
Hey guys! Thanks again for your responses. I removed $translateProvider.uses('fr_FR'); from my app.js and its works great. Thank you! |
I'd like to suggest re-opening this issue. I think the root problem wasn't addressed. It seems to me that the core problem is that if you register some loader in your app module's config method, then it will make the http call to grab the json strings file upon app module initialization. Which is fine for the actual app. But when trying to do unit testing, as soon as you try to It seems to be a chicken and egg problem because you can't use The only solution I've read about is to break out all your app "components" (i.e. services, filter, directives, controllers) into a different module from the main app module. Then during unit testing you only load the child modules and not the parent where the app initialization code (setting up of $translateProvider) happens. Seems sort of awkward to me but it is doable. Any other ideas? I'm not sure whether this is even an angular-translate problem, it may be more of an app module fundamental problem, but it seems one that needs solving if you want to unit test an app using angular-translate. |
@marplesoft hm.. looks like one has to mock the loader away. |
I've been struggling with this for days. If you can provide an example, that would be a huge help. |
I've provided a link, scroll a little bit upwards. It's in the test sources of the module itself. |
@marplesoft Maybe a more updated snippet would help out more: https://github.com/PascalPrecht/angular-translate/blob/master/test/unit/service/translate.spec.js#L439 |
Thanks @knalli and @PascalPrecht. But I think I'm still missing something. To illustrate the problem I'm having (and the problem it seems that everybody who uses angular-translate would have), I've published a simple example here: https://github.com/marplesoft/unitTestingExample You'll find a simple app that uses angular-translate with the static file loader and has one controller and one service. The app works fine. I've also written two unit tests which I run under karma/jasmine. You'll notice that I've employed your suggested approach to grab the However, you'll notice that my other unit test of my service fails with the following error:
I could do the same mocking of the translation loader here but that would mean that in every unit test suite I write - could be 20, 50, 100 of them - regardless of whether the code under test has anything at all to do with angular-translate, I'd have to mock the translate stuff. Which seems really onerous to me. Either I'm missing a best practice here or something is sort of broken about this, no? p.s. I'm not the only one with this problem, see here: |
Hi, @marplesoft. I am not familiar with testing well (or, even, "at all"). However, I'd like to say that there are some possible ways to go. At first, just like is said in the one of your links, one of the approaches is to break your application down into the modules. Such way, you could easily test your services which not are not dependent of translations. Another approach is to mock the loader. No matter how: by using translations(...) method or by providing a custom loader which is not dependent of $http. This way, it's not necessary to deal with $httpBackend all the time. But, yeah, I agree that these looks strange (or, even, mysterious). To be honest, I'm really surprised of such weird behavior of $httpBackend. As for me, the only logical way to deal with such kind of tests is to use $httpBackend to auto-respond all requests. So, if you'll find a reason why this approach doesn't work and a way how to solve this issue, please, leave a message here - I'd like to know too ) |
Hi @DWand - appreciate the thoughts. For your first suggestion, sure I could break out some services and controllers that don't explicitly use In regards to mocking the loader, yes that's what I've done in my example. It does work, but would require me to repeat that for every single unit test suite in the project regardless of whether it uses As I said earlier, I'm not so much blaming angular-translate for doing anything wrong. I'm not sure where the blame really lies, perhaps in the implementation of angular-mocks or a more fundamental aspect of angular's design. In any case, I can't imagine that I'm the first person to ever deal with this problem and there's got to be some kind of solution. Maybe there's a way to initialize the module once, mock the translate loader, then use that module instance for all test suites? |
@marplesoft @DWand @knalli Okay, so it turned out that it's pretty hard (or probably not even possible) to test angular apps that make use of angular-translate. The problem is clear. As soon as a module configuration uses a loader, it gets invoked as soon as When writing unit tests, this is indeed a chicken and egg situation, since the XHR (if the loader does one) gets called implicit rather than explicit. Which means one doesn't have any control over the execution of the loader since it just depends on the first instantiation of To be honest, I haven't written an app that uses an asynchronous loader yet, so I also didn't need to write any kind of tests for such case. But I see the problem. Once you load your app module into your tests via I'm not sure, but I wonder if it is possible to do something like: beforeEach(module('myApp', function ($provide, $translateProvider) {
// we're now within config() phase
// let's build a fake loader
$provide.factory('customLoader', function () {
// loader logic goes here
});
// maybe this would override the loader configuration from `myApp` ?
$translateProvider.useLoader('customLoader');
}));
it('should do sth.', function () {
inject(function ($translate) {
// $translate service gets injected the first time, and loader is called
// I wonder if now `customLoader` is called, or if its still the one configured
// in myApp.
});
}); @marplesoft would be great if you could test this. If this work, we would have finally a working solution. Otherwise, we probably have to introduce a new method that kicks of an asynchronous loader explicitly. |
@PascalPrecht Yep it's becoming clearer now. Your suggestion would probably work for that one test suite, but angular mocks appears to tear down the injector and module after every test. So it doesn't look like you can hold any state in the One approach I can think of to solve this is to have the test runner exclude the app module's config function and instead have an alternate config function implementation for testing. In the later, you could declare some other method of loading translation data (either just use in-line I might try this out today and post an example for feedback if I can get it working. |
Well, we have not a working test coverage for AngularJS in our projects yet. In context of the usage of
|
@marplesoft What's the problem with a after-every-test tear down of the module, as long as your module overrides a the used loader? This should actually be the solution. That's why things are wrapped in a So you actually don't want to hold a state from one test to another, instead what you actually want is to setup a fresh new environment for each spec aka each |
Yeah what you suggest is probably the only way to go. I still don't like having to spend cpu cycles setting up mocks for objects that aren't used - hundreds of times - when running my test suite but the perf cost probably isn't really that bad. The point I was stuck on initially was how to avoid having to repeat that loader mocking code for every test but the solution is probably obvious: create a helper function that does module setup and call that rather than calling the I'll work on updating my example project with this approach and then post it here. |
@marplesoft Maybe I get you wrong, but just doing beforeEach(module('youApp', function config(provider1, provider2) {
})); Should already do the trick. There's no helper function needed. |
@marplesoft regarding the perf. This shouldn't be a problem. Karma is really fast. angular-translate currently has around 300 tests. They all get executed within 1 sec.. Pretty sure you won't waste time :) |
So in that example, what is provider1? Is that what mocks the loader? |
I created a branch of that example project that uses the helper function I mentioned. It seems to work fairly well. See what you think. https://github.com/marplesoft/unitTestingExample/tree/moduleHelper In particular, see the following files: |
Yeap perfect.
|
Could you, just for testing purposes, change your helper to use a custom loader which doesn't make an XHR? |
I couldn't get that to work for some reason. Here's my helper: 'use strict';
function myappModule(translations) {
var DEFAULT_LANG = 'en';
var DEFAULT_TRANSLATIONS = {};
module('myapp', function config($provide, $translateProvider) {
$provide.factory('translateLoaderMock', function($q) {
return function loaderFn(options) {
var deferred = $q.defer();
return deferred.resolve(options);
}
});
$translateProvider.useLoader('translateLoaderMock', translations ||
DEFAULT_TRANSLATIONS);
$translateProvider.preferredLanguage(DEFAULT_LANG);
});
} Tests failed with:
Do you see anything I did wrong? |
I stumbled upon this issue as well, I used a global hook that runs for each one of the tests: With this I don't have to worry to remember on injecting the code to reset the translate provider on each fixture. |
@emiaj Thanks for sharing your insights! |
I'll provide some documentation in a later release about this topic. Closing this now. |
Hi, I am using your partial loader plugin and and have problems testing it. I was thinking about the way you're doing your gets. angular-translate-loader-partial.js lines 14-17: $http({ Why don't you give this a try: $http.get(this.parseUrl(urlTemplate, lang), {cache: $cacheFactory}) I assume the json files would be cached and we could simply test with $cacheFactory.put("apps_route_to_json_file", tests_route_to_json_file) This could work. Right? |
@PascalPrecht, @x057, give me few days to finish my exams, please, and I'll be with you. |
Hi again, I've hacked the angular-translate-loader-partial.js myself and used $templateCache for it. So the get should look like this:
And some lines below:
And of course line 10 should look like this:
Then in your tests before you start using .addPart or .uses add this:
It works perfect on my tests. Not 100% if templateCache is the ideal way to go though. But then again why not. Don't forget to preprocess json files...I actually preprocessed the json files with ng-html2js. Go figure. Anyway. Hope it helps. |
Okay, @x057, @PascalPrecht, as for testing I'm not 100% sure if it's needed for tests because partial loader has a But we actually can add an extra option to partial loader to use standard angular caching. Again, I'm not sure if this is a good idea, This way we'll get duplicate of the same functionality because we are storing translations by ourselves. So, this way we have rewrite some additional logic of partial loader. Possible problems are:
So, it's not so bad, but it could lead to some unpredictable behavior. |
@emiaj When using the global hook (i18n.spec.base), I get [$compile:multidir] Multiple directives error: ex: error: [$compile:multidir] Multiple directives [myDirectiveA, myDirectiveB] asking for template on <div id... Is there a way to prevent this from happening? |
Issue was when setting up directive unit tests - this had to be done in a particular way. Otherwise, we would get the "multiple directives" error. You need to move the directive setup code before the mock data and call an additional $digest after the mock data setup:
|
thank you!!! You are saving my job and my time... ;) |
I don't mean to reopen this issue, but I managed to "fix" my tests using this module: 'use strict';
angular.module('translateNoop', [])
.factory('$translateStaticFilesLoader', function ($q) {
return function () {
var deferred = $q.defer();
deferred.resolve({});
return deferred.promise;
};
}); Just include this in karma and import the module after your app: beforeEach(module('yourApp'));
beforeEach(module('translateNoop')); Hope this helps. Previous solutions in this thread were not working for me and this seems to be simpler. |
Sorry - not meaning to open this as well - however this could be useful: |
@ianmurrays worked for me! ty |
Hey guys,
I have a problem and I'm pretty sure it doesn't come from ngTranslate but I'll try to use your xp.
When I set my configuration of $translateProvider
When I run my test files, I get
Have you any idea why and how fix this ?
FYI I try ng-boilerplate project on a mac os x env.
Sorry for my bad english and thank you for your reading!
Happy coding!
The text was updated successfully, but these errors were encountered: