I have some ES5 example project that I would like to convert to ES6:
https://github.com/stefaneidelloth/testDemoES5
https://github.com/stefaneidelloth/testDemoES6
The examples include a class Qux which inherits from a class Baa.
When testing Qux, I would like to mock Baa.
For ES5 I use Squire.js to mock AMD module dependencies and the unit tests work just fine.
Unfortunately I could not find a testing framework that supports ES6 (="ECMAScript 2015 Language", ES2015) modules directly. We now have 2020 and there are still no unit tests for ES2015? I already spend a lot of time trying to get these tests working ... and I have the impression that my approach is missing something.
Since I could not find direct support for ES6 tests, I try to stick to karma and use webpack to translate the ES6 module code to ES5 AMD modules for testing.
Lets first consider to use karma in combination with requirejs and ES6 code that has been translated to AMD modules.
A. If I try to mock a translated class Baa (module 'src/baa') with Squire ... that does not work any more. Webpack puts all dependencies in a single file and when using 'src/qux', my injected 'src/baa' is not considered.
qux.test.js:
define([
'squire'
], function (
Squire
) {
describe('Qux', function(){
var sut;
beforeEach(function(done){
var injector = new Squire();
injector.mock('src/baa', createBaaMock());
injector.require([
'src/qux'
], function(
quxModule
){
var Qux = quxModule.default;
sut = new Qux('qux');
done();
});
});
it('quxMethod', function(){
expect(sut.quxMethod()).toEqual('quxMethod');
});
it('baaMethod', function(){
expect(sut.baaMethod()).toEqual('baaMockedMethod');
});
it('overridableMethod', function(){
expect(sut.overridableMethod()).toEqual('qux');
});
function createBaaMock(){
var BaaMock = function (name) {
this.name = name;
};
BaaMock.prototype.baaMethod = function () {
return 'baaMockedMethod';
}
var moduleMock = {
default: BaaMock
}
return moduleMock;
}
});
});
=> I get the error
Expected 'baaMethod' to equal 'baaMockedMethod'.
Some furhter info on debugging.... The translation to ES5 has the disadvantage that when debugging tests, the code that runs in the browser looks different than the original code (by default). Therefore, possible bugs are harder to identify. What helps here is to:
use the webpack mode "development" instead of "production" to avoid minification
enable the devtool option of webpack to enable source mapping. This way, the original code is also shown in the browser when debugging.
B. I tried to use inject-loader, an alternative to Squire.js, which knows about webpack:
https://github.com/LeanKit-Labs/inject-loader
However, that seems to be a CommonJs module which is not compatible to my karma + requirejs project:
qux.test.js:
describe('Qux', function(){
var sut;
beforeEach(function(done){
require(['inject!src/qux'],function(ModuleInjector){
var quxModule = ModuleInjector({
'src/baa': crateBaaMock()
});
var Qux = quxModule.default;
sut = new Qux('qux');
done();
});
});
it('quxMethod', function(){
expect(sut.quxMethod()).toEqual('quxMethod');
});
it('baaMethod', function(){
expect(sut.baaMethod()).toEqual('baaMockedMethod');
});
it('overridableMethod', function(){
expect(sut.overridableMethod()).toEqual('qux');
});
function createBaaMock(){
var BaaMock = function (name) {
this.name = name;
};
BaaMock.prototype.baaMethod = function () {
return 'baaMockedMethod';
}
var moduleMock = {
default: BaaMock
}
return moduleMock;
}
});
=> I get the error
Uncaught ReferenceError: module is not defined.
I also tried mock-loader but did not get it working.
C. I tried to not use AMD modules but CommonJs modules as target for the webpack compilation. However, I did not manage to use the commonjs proprocessor and the and webpack preprocesser of karma together.
D. I tried to use system.js instead of require.js and webpack with Karma. However, karma-system.js relies on a very old version of system.js (0.19.47) and I did not get it working.
E. In an answer to a related and old SO question someone suggests to use "import * as obj" style to import a class in form of a module and then spy on the default export to mock the class.
However, that might cause issues if several tests are using that "modified module" (the property "default" can not be redefined).
Since webpack does not dynamically load dependencies, following test fails:
define([
'src/baa',
'src/qux'
],function(
baaModule,
quxModule
){
describe('Qux', function(){
var sut;
beforeEach(function(done){
baaModule.default = createBaaMock();
var Qux = quxModule.default;
sut = new Qux('qux');
done();
});
it('quxMethod', function(){
expect(sut.quxMethod()).toEqual('quxMethod');
});
it('baaMethod', function(){
expect(sut.baaMethod()).toEqual('baaMockedMethod');
});
it('overridableMethod', function(){
expect(sut.overridableMethod()).toEqual('qux');
});
function createBaaMock(){
var BaaMock = function (name) {
this.name = name;
};
BaaMock.prototype.baaMethod = function () {
return 'baaMockedMethod';
}
var moduleMock = {
default: BaaMock
}
return moduleMock;
}
});
});
In summary, I found a lot of outdated and incomplete approaches for testing ES6 modules and none of them seems to work out just nicely.
=> If I should stay with karma, how do I need to adapt my test qux.test.js example code (and probably my configuration files) to correctly mock the class Baa?
=> Is it possible to tell webpack to keep the translated modules separate, so that I can easily inject the dependencies with Squire.js?
=> Is there a better and up to date work flow/framework for unit testing ES6 modules in the browser? Did someone try to combine jest with karma?
Related stuff:
https://zirho.github.io/2016/06/06/karma-es6/
How to mock dependencies for unit tests with ES6 Modules
https://www.npmjs.com/package/inject-loader
https://github.com/LeanKit-Labs/amd-inject-loader
https://jestjs.io/
https://www.npmjs.com/package/proxyrequire
ES6 unit testing without compilation
Webpack: compile folder but keep separate files?
https://www.reddit.com/r/javascript/comments/44vngp/es6_webpack_unittesting_i_need_help/
How to disable bundling in Webpack for development?
Disable Bundling from Webpack
https://blog.oharagroup.net/mocking-es2015-imports-in-webpack-2-without-loaders-1dec44365989
I switched from karma to jest and here is a working demo project:
https://github.com/stefaneidelloth/testDemoES6Jest
The workflow is still based on a transpiler (babel) but that happens in the background and does not really influence the development experience.
Example test code that mocks some ES6 module:
import Qux from './../src/qux.js';
jest.mock('./../src/baa.js', () => {
return class BaaMock {
constructor(name){
this.name = name;
}
baaMethod(){
return 'baaMockedMethod';
}
}
});
describe('Qux', function(){
var sut;
beforeEach(function(){
sut = new Qux('qux');
});
it('quxMethod', function(){
expect(sut.quxMethod()).toEqual('quxMethod');
});
it('baaMethod', function(){
expect(sut.baaMethod()).toEqual('baaMockedMethod');
});
it('overridableMethod', function(){
expect(sut.overridableMethod()).toEqual('qux');
});
});
Example package.json (with code coverage enabled for test command):
{
"name": "testDemoES6Jest",
"version": "1.0.0",
"main": "index.js",
"repository": "https://github.com/stefaneidelloth/testDemoES6Jest.git",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"#babel/core": "^7.5.5",
"#babel/preset-env": "^7.5.5",
"babel-jest": "^24.8.0",
"jest": "^24.8.0",
"jest-cli": "^24.8.0",
"requirejs": "2.3.6"
},
"scripts": {
"test": "jest --coverage --collectCoverageFrom src/**/*.js ",
"debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --watch --runInBand"
}
}
For debugging there are several options, for example:
a) VisualStudioCode, together with the Jest plugin
(Debug=>Install additional Debuggers=> Jest ("Use Facebook's Jest with Pleasure", https://github.com/jest-community/vscode-jest)
Example debug configuration launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest Tests",
"program": "${workspaceRoot}\\node_modules\\jest\\bin\\jest.js",
"args": [
"-i"
],
"internalConsoleOptions": "openOnSessionStart"
}
]
}
b) Google Chrome:
Run following console command:
node --inspect-brk ./node_modules/jest/bin/jest.js
(can be saved as alias within your packages.json under scripts: {"debug": ... and run with npm run-script debug)
Open Chrome browser and enter the address
chrome://inspect
Click on Open Dedicated DevTools for Node
Drag and drop your project directory to the dev tools to allow file access (only required once)
Open the file you would like to debug and set a break point
Click continue in the dev tools to continue to your wanted break point
Also see
https://artsy.github.io/blog/2018/08/24/How-to-debug-jest-tests/
c) Webstorm
See https://blog.jetbrains.com/webstorm/2018/10/testing-with-jest-in-webstorm/
For tests in browsers also see
https://github.com/smooth-code/jest-puppeteer
https://developers.google.com/web/tools/puppeteer/get-started
Related
Got this weird bug when running the jest test, one of the UI component from a self defined UI package keeps throwing error, saying that an object in that package is undefined...
The component itself works perfectly fine, and the same component's testing logic works in another repo without nextjs, and that repo utilize #swc/jest for js transform in jest.config file.
I've also added that package itself to transformIgnorePatterns in jest-config file, but somehow the bug still presents...
The project itself is in nextjs, and below is a snapshot of the jest.config file
/** #type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
testPathIgnorePatterns: [
'<rootDir>/.next/',
'<rootDir>/node_modules/',
'<rootDir>/e2e/'
],
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['#testing-library/jest-dom/extend-expect'],
setupFiles: [require.resolve('whatwg-fetch')],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }]
},
transformIgnorePatterns: ['/node_modules/myPackage', 'jest-runner'],
testMatch: ['**/*.spec.{js,jsx,ts,tsx}'],
};
and the error itself goes Error: Uncaught [TypeError: Cannot read property 'object' of undefined], which tracks down to /node_modules/myPackage
how the package is used
import { InputBox } from 'myPackage';
const MyComponent = () => {
return (
<div>
<InputBox />
</div>
);
}
export default MyComponent;
and here's the test:
import { act, render } from '#testing-library/react';
import React from 'react';
describe('show component', () => {
it('should render', async () => {
await act(async () => {
render(
<MyComponent/>
);
});
});
});
I've found a similar question on stackoverflow Jest: Cannot read property of undefined when importing from own package
but that one is using regular js, and this one being next.js, there's really nowhere I can update .babelrc to update those configs...
Any input would be appreciated.
Update: it turns out the component that causes me bug is built on top of react-popper library, which is built on top of popper.js library. popper.js library doesn't support jsdom by default, and it requires to do jest mock. But my library is 2 layers abstractions on top of popper.js library, I'm not sure how to do that, or even if that is doable...
Background
I recently learned about CLASP and became excited about the possibility of using TDD to edit my Google Apps Scripts (GAS) locally.
NOTE: there might be a way to write tests using the existing GAS editor, but I'd prefer to use a modern editor if at all possible
clasp works great, but I cannot figure out how to mock dependencies for unit tests (primarily via jest, though I'm happy to use any tool that works)
I got farthest by using the gas-local package, and was able to mock a single dependency within a test
However I could not find a way to mock multiple dependencies in a single test/call, and so I created this issue
Challenge
Despite installing #types/google-apps-script, I am unclear on how to "require" or "import" Google Apps Script modules whether using ES5 or ES2015 syntax, respectively--see below for an illustration of this.
Related StackOverflow Post
Although there is a similar SO question on unit testing here, most of the content/comments appear to be from the pre-clasp era, and I was unable to arrive at a solution while following up the remaining leads. (Granted, it's very possible my untrained eye missed something!).
Attempts
Using gas-local
As I mentioned above, I created an issue (see link above) after trying to mock multiple dependencies while using gas-local. My configuration was similar to the jest.mock test I describe below, though it's worth noting the following differences:
I used ES5 syntax for the gas-local tests
My package configuration was probably slightly different
Using jest.mock
LedgerScripts.test.js
import { getSummaryHTML } from "./LedgerScripts.js";
import { SpreadsheetApp } from '../node_modules/#types/google-apps-script/google-apps-script.spreadsheet';
test('test a thing', () => {
jest.mock('SpreadSheetApp', () => {
return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
return { getActiveSpreadsheet: () => {} };
});
});
SpreadsheetApp.mockResolvedValue('TestSpreadSheetName');
const result = getSummaryHTML;
expect(result).toBeInstanceOf(String);
});
LedgerScripts.js
//Generates the summary of transactions for embedding in email
function getSummaryHTML(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = ss.getSheetByName("Dashboard");
// Do other stuff
return "<p>some HTML would go here</p>"
}
export default getSummaryHTML;
Result (after running jest command)
Cannot find module '../node_modules/#types/google-apps-script/google-apps-script.spreadsheet' from 'src/LedgerScripts.test.js'
1 | import { getSummaryHTML } from "./LedgerScripts.js";
> 2 | import { SpreadsheetApp } from '../node_modules/#types/google-apps-script/google-apps-script.spreadsheet';
| ^
3 |
4 | test('test a thing', () => {
5 | jest.mock('SpreadSheetApp', () => {
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:307:11)
at Object.<anonymous> (src/LedgerScripts.test.js:2:1)
For reference, if I go to the google-apps-script.spreadsheet.d.ts file that has the types I want, I see the following declarations at the top of the file...
declare namespace GoogleAppsScript {
namespace Spreadsheet {
...and this one at the bottom of the file:
declare var SpreadsheetApp: GoogleAppsScript.Spreadsheet.SpreadsheetApp;
So maybe I am just importing SpreadsheetApp incorrectly?
Other files
jest.config.js
module.exports = {
clearMocks: true,
moduleFileExtensions: [
"js",
"json",
"jsx",
"ts",
"tsx",
"node"
],
testEnvironment: "node",
};
babel.config.js
module.exports = {
presets: ["#babel/preset-env"],
};
package.json
{
"name": "ledger-scripts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"author": "",
"license": "ISC",
"dependencies": {
"#babel/core": "^7.11.1",
"#babel/preset-env": "^7.11.0",
"#types/google-apps-script": "^1.0.14",
"#types/node": "^14.0.27",
"babel-jest": "^26.3.0",
"commonjs": "0.0.1",
"eslint": "^7.6.0",
"eslint-plugin-jest": "^23.20.0",
"gas-local": "^1.3.1",
"requirejs": "^2.3.6"
},
"devDependencies": {
"#types/jasmine": "^3.5.12",
"#types/jest": "^26.0.9",
"jest": "^26.3.0"
}
}
Note: the scope of your question is broad and may require clarification.
clasp works great, but I cannot figure out how to mock dependencies for unit tests (primarily via jest, though I'm happy to use any tool that works)
You don't need Jest or any particular testing framework to mock the global Apps Script objects.
// LedgerScripts.test.js
import getSummaryHTML from "./LedgerScripts.js";
global.SpreadsheetApp = {
getActiveSpreadsheet: () => ({
getSheetByName: () => ({}),
}),
};
console.log(typeof getSummaryHTML() === "string");
$ node LedgerScripts.test.js
true
So maybe I am just importing SpreadsheetApp incorrectly?
Yes, it is incorrect to import .d.ts into Jest.
Jest doesn't need the TypeScript file for SpreadsheetApp. You can omit it.
You only need to slightly modify the above example for Jest.
// LedgerScripts.test.js - Jest version
import getSummaryHTML from "./LedgerScripts";
global.SpreadsheetApp = {
getActiveSpreadsheet: () => ({
getSheetByName: () => ({}),
}),
};
test("summary returns a string", () => {
expect(typeof getSummaryHTML()).toBe("string");
});
Despite installing #types/google-apps-script, I am unclear on how to "require" or "import" Google Apps Script modules whether using ES5 or ES2015 syntax
#types/google-apps-script does not contain modules and you do not import them. These are TypeScript declaration files. Your editor, if it supports TypeScript, will read those files in the background and suddenly you'll have the ability to get autocomplete, even in plain JavaScript files.
Additional comments
Here you check that a function returns a string, perhaps just to make your example very simple. However, it must be stressed that such testing is better left to TypeScript.
Since you returned an HTML string, I feel obligated to point out the excellent HTML Service and templating abilities of Apps Script.
Unit testing or integration testing? You mention unit testing, but relying upon globals is generally a sign you might not be unit testing. Consider refactoring your functions so they receive objects as input rather than calling them from the global scope.
Module syntax: if you use export default foo, you then import without curly braces: import foo from "foo.js" but if you use export function foo() { then you use the curly braces: import { foo } from "foo.js"
How can I make mocha run with System.js handling all require/import statements? I have a React component which does an import of CSS file. When running on browser System.js gets loaded and it's plugin-css handles import of css files. But when running as a Mocha unit test import of a css file causes Mocha to crash.
import '../../../dist/css/common/NavBar.css!';
throws error
Error: Cannot find module '../../../dist/css/common/NavBar.css!'
Does anyone have a similar test setup up and running ?
You're probably gonna need to start using Karma and a plugin for System.js
https://www.npmjs.com/package/karma-systemjs
Here's a karma.conf.js to get you started:
module.exports = function(config) {
config.set({
browsers: [ 'Chrome' ],
singleRun: false,
frameworks: ['mocha', 'sinon', 'systemjs'],
plugins: [
'karma-systemjs'
'karma-chrome-launcher',
'karma-chai',
'karma-mocha',
'karma-sourcemap-loader',
'karma-spec-reporter',
'karma-mocha-reporter',
'karma-sinon'
],
files: [
'test/bootstrap.js'
],
reporters: [ 'spec' ],
systemjs: {
// Path to your SystemJS configuration file
configFile: 'app/system.conf.js',
// Patterns for files that you want Karma to make available, but not loaded until a module requests them. eg. Third-party libraries.
serveFiles: [
'lib/**/*.js'
],
// SystemJS configuration specifically for tests, added after your config file.
// Good for adding test libraries and mock modules
config: {
paths: {
'angular-mocks': 'bower_components/angular-mocks/angular-mocks.js'
}
}
}
});
};
and in test/bootstrap.js perhaps this:
//test/bootstrap.js
//put this in your test directory (include it with your tests or test recursively)
// setup globals
chai = require('chai');
chai.should();
chai.use(require('chai-things'));
assert = require('chai').assert;
expect = require('chai').expect;
should = require('chai').should;
React = require('react/addons');
global.TestUtils = React.addons.TestUtils;
ReactTools = require('react-tools');
reactStub = React.createClass({
displayName: 'StubClass',
mixins: [],
render: function() {
return null;
}
});
// setup
before(function(){
});
beforeEach(function(){
});
// teardown
afterEach(function() {
});
after(function(){
});
// include all the tests
var context = require.context('./', true, /-test\.js$/);
context.keys().forEach(context);
I got it working in nodejs itself. I just had to stub out imports to css files. Rest of stuff babel handles. This is my require file which mocha uses.
process.env.BABEL_DISABLE_CACHE = 1;
require('babel-core/register')({
'optional': [
'es7.classProperties'
],
'resolveModuleSource': function (source) {
if (source.indexOf('dist/css') !== -1) {
return '../../../test/css-dummy.js';
}
return source;
},
'blacklist': []
});
Scenario
I am in the process of writing a number of jasmine tests for a Durandal based app that I am in the process of writing. The Durandal documentation suggests that the way to write tests is like
ViewModel
define([
'knockout',
'plugins/router',
'services/unitofwork',
'services/logger',
'services/errorhandler',
'services/config'
],
function (ko, router, unitofwork, logger, errorhandler, config) {
var uow = unitofwork.create();
var searchTerm = ko.observable();
var results = ko.observableArray([]);
var search = function () {
uow.myySearch(searchTerm).then(function (data) {
results(data);
logger.log(data.length + ' records found', '', 'myViewModel', true);
});
};
var vm = {
search : search,
searchTerm : searchTerm,
results : results
};
});
Test
define(['viewmodels/myViewModel'], function (myViewModel) {
describe('Stuff im testing', function(){
it('returns true', function () {
expect(true).toBe(true);
});
});
});
and for most of my tests this works great.
Problem
How do I mock/stub/fake a module that has been passed into ViewModel. For instance the UnitOfWork module so that it always returns a standard set of data.
For unit testing check out https://github.com/iammerrick/Squire.js/ a dependency mocker for requirejs. Another technique using require context is described in How can I mock dependencies for unit testing in RequireJS?.
For integration testing you might look into something like http://saucelabs.com (selenium based).
For some grunt tasks that helps setting up unit tests in phantomjs|browser see https://github.com/RainerAtSpirit/HTMLStarterKitPro (Disclaimer: I'm the maintainer of the repo). I'd love to see some mockup integration, so send a pull request if you feel inclined.
Check this out
https://github.com/danyg/jasmine-durandal
this is a library that I'm working on, in a few days will have the ability to test widgets too.
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
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.
We took the approach of ignoring the translation loader in unit tests, rather than being forced to modify each of the spec files.
One way to do it could be by separating the loader configuration to a separate file and then exclude it in karma.
So for example you can create a file app-i18n-loader.js (all other module configurations takes place in a different file):
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');
}
And in your karma.conf.js exclude the file:
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'
],
(Note: Answer edited to a solution that does not require grunt/gulp).
I wanted a solution,
which was not too hacky
which didn't require me to change my actual application code,
which wouldn't interfere with the ability to load additional modules
and most importantly which wouldn't require me to change every
single test.
This is what I ended up with:
// 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 () {
};
}));
Assuming you don't need the actual translations for your unit tests, this works great. Just put the beforeEach on a global level, preferably in it's own file inside the test folder. It will be executed before every other test then.
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');
Try putting to test method:
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
}
See examples from Angular docs
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.
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.
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
I use this pattern.
ApplicationModule set regular angular-translate config.
test code load 'testModule' instead of '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");
})
})
Late to the table with this, but I got round this by specifying that Karma simply serve the files as per this entry in karma.conf.js:
files: [
...
{pattern: 'scripts/i18n/*.json', included: false, served: true},
...
]
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);