When writing unit tests for a React Native project I want to be able to test different snapshots based on different platforms.
I first tried jest.mock to mock Platform but seems to be async. This approach does work when I have two separate files, but I'd prefer to keep everything in one file if possible.
I tried jest.doMock because of this snippet from the documentation:
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
https://facebook.github.io/jest/docs/en/jest-object.html#jestdomockmodulename-factory-options
However I'm still seeing undesirable results. When I console.log in the android test I see that Platform.OS is whatever I set the first doMock to be.
I also tried wrapping the mock in a beforeEach in a describe becasue I thought that might help with scoping
http://facebook.github.io/jest/docs/en/setup-teardown.html#scoping
describe('ios test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'ios';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('android test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'android';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
Any ideas on how I can change the mock Platform for tests in the same file?
There are a lot of suggestions on how to solve this problem in another question, but none of them worked for me either, given the same requirements you have (tests for different OSs in the same suite file and in one test run).
I eventually worked around it with a somewhat clunky trivial helper function that can be mocked as expected in tests – something like:
export function getOS() {
return Platform.OS;
}
Use it instead of Platform.OS in your code, and then simply mock it in your tests, e.g.
it('does something on Android', () => {
helpers.getOS = jest.fn().mockImplementationOnce(() => 'android');
// ...
}
That did the trick; credit for the idea is due to this guy.
I need help with testing action which using document.location.
I'm running react/redux + sinon/mocha.
Here is my action:
export function importFile(file) {
return (dispatch) => {
dispatch(importFile());
return jQuery.post('/api/import/', {file}).done((data) => {
window.location = `/edit/${data.id}`;
}).fail((err) => {
return dispatch(importFileError(err));
});
};
}
If u run my test in command line - i can not check if there was a redirect to page after done, but if i run my test in browser i had redirect and all other tests was dropped.
Here is my test:
it('should create an action to upload import file', (done) => {
const id = '123';
const file = 'testname';
const server = sinon.fakeServer.create();
const expectedActions = [
{type: actions.IMPORT_FILE}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(actions.importFileUpload(file));
server.respondWith([201, { 'Content-Type':'application/json' }, `{"id":"${id}","version":12}`]);
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest();
server.respond();
});
What is the best way to test functions like this? Prevent redirect or use a wrapper for redirect function?
Thanks.
I have a fairly complex piece of code I test with Mocha, in order to isolate it from Mocha I run it in an iframe. This prevents my application from messing up the Mocha tests. It should work also to prevent assignments to window.location from stopping the tests as window.location is local to the frame. (The Window object in the frame is a different object from the one in the page that contains the frame.)
I have the following directive:
function TopLevelMenuDirective ($userDetails, $configuration) {
return {
restrict:'A',
templateUrl: staticFilesUri + 'templates/TopLevelMenu.Template.html',
scope: {
activeTab: '='
},
link: function (scope, element, attributes) {
var userDetails = $userDetails;
if ($userDetails) {
scope.user = {
name: userDetails.name ? userDetails.name : 'KoBoForm User',
avatar: userDetails.gravatar ? userDetails.gravatar: (staticFilesUri + '/img/avatars/example-photo.jpg')
};
} else {
scope.user = {
name: 'KoBoForm User',
avatar: staticFilesUri + '/img/avatars/example-photo.jpg'
}
}
scope.sections = $configuration.sections();
scope.isActive = function (name) {
return name === scope.activeTab ? 'is-active' : '';
}
}
}
}
I want to mock the dependencies to unit test the different code paths with values known by the unit tests. I have the following sample unit test:
it('should set $scope.user to values passed by $userDetails',
inject(function($compile) {
var element = '<div top-level-menu></div>';
element = $compile(element)($scope);
$scope.$apply();
expect(element.isolateScope().user.name).toBe('test name');
expect(element.isolateScope().user.avatar).toBe('test avatar');
}
));
This gives me two problems.
First, since the template is in an external file, when it loads it tries to fetch it and errors out beacause the file is nowhere to be found, which is logical since it's in a test environment and not an actual server.
Second, there's no apparent way to mock the dependencies injected into the directive through its constructor. When testing controllers you can use the $controller service, but since directives are instantiated indirectly by compiling an html tag with a passed scope, there's no way to instantiate it directly (e.g. there's no analogous $directive). This impedes me from setting $userDetails.name and $userDetails.gravatar to 'test name' and 'test avatar' respectively.
How do I get the directive to compile properly and run with a custom $userDetails dependency?
To load the template file you must configure karma-ng-html2js-preprocessor in karma.
First, visit this page and follow the installation instructions. Then, you need to add a couple of entries in your karma.config.js file:
files: [
'templates/*.html'
],
this tells karma to load all html files in the templates folder (if your templates are somewhere else, put that folder there).
preprocessors: { '**/*.html': 'ng-html2js' },
this tells karma to pass all html files through the ng-html2js preprocessor, which then transforms them into angular modules that put the templates into the $templateCache service. This way, when $httpBackend queries the "server" for the template, it get's intercepted by the template cache and the correct html is returned. All fine here, except for the template's URL: it has to match the templateUrl property in the directive, and ng-html2js passes the full path as the uri by default. So we need to transform this value:
ngHtml2JsPreprocessor: {
cacheIdFromPath: function(filepath) {
var matches = /^\/(.+\/)*(.+)\.(.+)$/.exec(filepath);
return 'templates/' + matches[2] + '.' + matches[3];
}
},
this receives filepath and passes it through a regular expression that extracts the path, file name and extension into an array. You then prepend 'templates/ to the file name and extension and you get the expected uri.
After all this is done making the template available is a matter of loading the module before your test is run:
beforeEach(module('templates/TopLevelMenu.Template.html'));
keep in mind, module is an external service located in angular-mocks.js.
for injecting a custom service into the directive you need to override the service's provider:
beforeEach(module(function ($provide) {
$provide.provider('$userDetails', function () {
this.$get = function () {
return {
name: 'test name',
gravatar: 'test avatar'
};
}
});
}));
$provide is the service that provides your providers. So, if you want to inject a mock dependency you override the provider here.
With that code executing before your test you'll have a mock $userDetails service that returns your predefined strings.
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);