Test spec executing before $rootScope.$apply() is done resolving promise - javascript

I'm writing angular unit tests using Jasmine with angular-mocks' httpBackend.
I've managed to properly mock my backend but some one of my tests has some issues with an http call where a value is set to scope after the request is done (in then()).
Controller:
$scope.startShift is binded to a button click.
app.controller('ShiftStartCntl', function($scope, $rootScope, $location, Restangular, $http, shifts) {
$scope.shifts = shifts;
$scope.startShift = function (category) {
$scope.startingShift = true;
baseShifts.post({category_number: 2})
.then(function (shift) {
$scope.shift = shift;
// breaks here if run as the endpoint isn't mocked
$http.post('endpointThatIsNotMocked').then(function() {});
$location.path($scope.rootURL + '/shift/' + shift.id + '/');
$scope.startingShift = false;
});
});
Test:
// module definition and stuff
//
// [...]
//
describe('ShiftStartCntl', function() {
var scope, rootScope, httpBackend, ShiftStartCntl;
beforeEach(angular.mock.inject(function($rootScope, $controller, $httpBackend) {
scope = $rootScope.$new();
rootScope = $rootScope;
httpBackend = $httpBackend;
// mimics the controller's ng-route `resolve` dependency
shifts = getJSONFixture('leave_shifts.json');
scope.baseShifts = baseApi.all('shifts');
// used in the controller
scope.categories = getJSONFixture('leave_categories.json');
httpBackend.expectPOST('/api/shifts/', {category_number: 2})
.respond(203, {"id": 4, "user": 1,
"category_number": 2,
"sign_in": "2015-02-10T21:29:06.110Z",
"sign_out": null
});
$controller('ShiftStartCntl', {$scope: scope, shifts: []});
}));
it('should start a shift', function() {
expect(shifts.length).toEqual(2);
expect(scope.startingShift).toBe(undefined);
scope.startShift(scope.categories[1]);
rootScope.$apply();
expect(scope.shift.id).toEqual(4);
httpBackend.flush();
});
});
The produced error is the following:
PhantomJS 1.9.8 (Mac OS X) Shifts ShiftStartCntl should start a shift FAILED
Expected undefined to equal 4.
at [...]/js/tests/unit/controllers/main.js:100
Error: Unexpected request: POST yoyoyo
which is the line where expect(scope.shift.id).toEqual(4);
My problem is that the error about the unexpected request should happen before expect(scope.shift.id).toEqual(4); is executed.
As read in several Stackoverflow answers and various blogposts, I've tried using $rootScope.apply(); with no success.
Any ideas on how this could be solved?

Can you try to move the httpBackend.flush(); two lines above
scope.startShift(scope.categories[1]);
httpBackend.flush();
rootScope.$apply();
expect(scope.shift.id).toEqual(4);

Related

Debug mock httpbackend requests

I have a situation where there are many mocked http requests. While working on angular upload, something fishy happening in my case. It is always throwing status:200 success and html complete body response.
Below is my angular upload code:
function fileUploadController(FileUploader) {
/* jshint validthis: true */
let vm = this;
vm.type = this.type;
vm.clientId = this.client;
let uploader = new FileUploader({
url: 'http://localhost:8001/prom-scenario-config/files?clientId=' + vm.clientId,
data: {type: vm.type}
});
vm.uploads = {uploader};
vm.upload = upload;
vm.uploads.uploader.queue = [];
vm.uploads.uploader.onCompleteItem = function (item, response) {
let type = item.uploader.data.type;
console.log('response => ', response);
};
}
mock of httpbackend code looks like this:
$httpBackend.whenPOST(new RegExp('http://localhost:8001/prom-scenario-config/files\\?clientId=([a-zA-Z0-9-])$$'))
.respond(function () {
return [200, 'foo'];
});
But there is no affect on this.
Is there any error in my regex code in constructing?
With or without having the mock code. Still the response i am receiving is 200.
There are so many mock requests, i am facing difficulty in identifying which request is being called.
Is there any tricky way to identify which regex call is called? Or enforce my request to mock?
Below is the reference for status and response FYI.
Unit test suppose that a unit is tested in isolation. Any other thing which is not a tested unit, i.e. a controller should be mocked, especially third-party units.
Considering that it is tested with Jasmine, FileUpload service should be stubbed:
beforeEach(() => {
// a spy should be created inside beforeEach to be fresh every time
module('app', { FileUpload: jasmine.createSpy() });
});
And then controller is tested line by line like:
it('...', inject(($controller, FileUpload) => {
const ctrl = $controller('fileUploadController');
...
expect(FileUpload).toHaveBeenCalledTimes(1);
expect(FileUpload).toHaveBeenCalledWith({ url: '...', type: {...} });
// called with new
const fileUpload = FileUpload.calls.first().object;
expect(fileUpload instanceof FileUpload).toBe(true);
expect(ctrl.fileUpload).toBe(fileUpload);
expect(fileUpload.onCompleteItem).toEqual(jasmine.any(Function));
expect(fileUpload.queue).toEqual([]);
...
}));
In the code above clientId=([a-zA-Z0-9-]) regexp part matches only ids consisting of a single character, which isn't true. That's why it is preferable to hard-code values in unit tests, human errors are easier to spot and detect. It's not possible to unambiguously identify the problem when the tests are too loose, this results in wasted time.

Jasmine / Karma HTTP not mocked as expected (Angular Controller on REST call)

i've been searching around for the last days but i cannot get closer to the solution. I try to mock the http response requested by the angular controller.
angular controller:
myController = function ($http, appConfig) {
$http.post(appConfig.restPath+"/administration/imports", {
}).then(function(data){
$scope.pagination = data;
$scope.totalItems = data.data.content.length;
$scope.totalPages = data.data.totalPages;
$scope.pages = [];
$scope.imports = data.data;
for (var i = 0; i < $scope.totalPages; i++){
$scope.pages.push(i);
}
});
}
and the test:
describe('Controller: myController', function() {
// load the controller's module
beforeEach(module("myModule"));
var controller,
scope, httpBackend, myPost;
// Initialize the controller and a mock scope
beforeEach(inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
myPost = httpBackend.whenPOST('http://localhost:9000/api/v1/administration/imports').respond({data: {content: ["a", "b"], totalPages: "1"}}, "");
scope = $injector.get('$rootScope');
var $controller = $injector.get('$controller');
createController = function() {
return $controller(myController, {'$scope' : scope });
};
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should browse the list of imported files', function() {
httpBackend.expectPOST('http://localhost:9000/api/v1/administration/imports');
var controller = createController();
httpBackend.flush();
});
});
But it seems that he wants to ask the server for the real data when i inspect the test in the chrome console (network traffic -> HTTP requests shows me, that he is requesting the server instead of loading the mocked data...), but he receives 403 (forbidden).
the error i receive by karma is the following:
TypeError: Cannot read property 'length' of undefined at myController.js:35:40
line 35 is:
$scope.totalItems = data.data.content.length;
that makes me think that he tries to load the data from the REST service, receives 403, empty result (means data == {}) and then he tries to access on data.data.content.length which is undefined....
as you can see i did it exactly like google it recommends...
https://docs.angularjs.org/api/ngMock/service/$httpBackend
Other examples on SO or anywhere else look quite similar. What am i doing wrong?
yes you have to provide the real data or at least you can provide a prototype pf your data because you are testing that unit and it requires length.
and remove this part because you are mocking it twice
httpBackend.expectPOST('http://localhost:9000/api/v1/administration/imports');
use
myPost = httpBackend.expectPOST('http://localhost:9000/api/v1/administration/imports').respond({data: {content: ["a", "b"], totalPages: "1"}}, "");
this way you make sure that post is being called but if you still get same error then you have to check respond data.

Application is undefined in AngularJS

I am trying to create a AngularJS and WEB API application but this is my first time. I am getting following error...
0x800a1391 - JavaScript runtime error: 'MyApp' is undefined
My AngularJS Service file is like below and this is where it gives the error above...
MyApp.factory('CityService', ['$http', function ($http) {
var urlBase = 'http://localhost:58057/api';
var CityService = {};
CityService.getCities = function () {
return $http.get(urlBase + '/Cities');
};
return CityService;
}]);
Where should I define MyApp?

AngularJS Unknow provider

I tried to set a new controller in my Angular app, but I have this error coming:
[$injector:unpr] http://errors.angularjs.org/1.4.2/$injector/unpr?p0=successRedirectProvider%20%3C-%20successRedirect%20%3C-%20ingreCtrl.
I tried many things for a few hours but still have this issue.
Here's my files:
app.js:
var app = angular.module('app', ['formSubmit']);
app.factory('successRedirect', function(){
return function(data) {
if(data.status === "success") {
alert(data.message);
if (typeof(data.redirect) !== "undefined"){
document.location.href = data.redirect;
}
}else{
}
};
});
app.factory('errors', function(){
return function(data) {
alert(data.message)
for(var i = 0; i<data.errors.length;i++){
$('#new-page-form-container').append('<p>'+data.errors[i]+'</p>');
}
};
});
formApp.js:
var formSubmit = angular.module('formSubmit', ['ckeditor', 'ngFileUpload']);
ingredientsCtrl.js:
formSubmit.controller('ingreCtrl', ['$scope', '$filter', '$http', 'successRedirect', 'errors', function ($scope, $filter, $http, successRedirect, errors) {
}]);
You're trying to use the successRedirect service of the app module inside your formSubmit module. That means you need to dependency inject app into formSubmit:
var formSubmit = angular.module('formSubmit', ['app', 'creditor', 'ngFileUpload']);
^^^^^
Not the other way around.
I finally found why it doesn't work, last week I did another module for the login system in my web site in another file, and I didn't remember that I already gave the name 'app' to this module, so I change my module's name in the file app.js and it works.
But to answer to some comments, my dependency injection was good, as my inclusion, no need to change the order. The problem was the module name.
Thanks anyway for your time, subject close ^^

Protractor times out waiting for sync with page when using $resource

I'm testing Protractor with a small AngularJS app.
This is the test:
describe('Testing Protractor', function() {
var draftList;
it('should count the number of drafts', function() {
browser.get('#/');
draftList = element.all(by.repeater('newsletter in drafts'));
expect(draftList.count()).toEqual(2);
});
});
Controller:
angular.module('myApp.controllers', []).
controller('DraftsCtrl', ['$scope', 'Draft', function($scope, Draft) {
$scope.drafts = Draft.query();
}])
Draft service:
angular.module('myApp.services', ['ngResource']).
factory('Draft', ['$resource',
function($resource) {
return $resource('api/drafts/:id')
}])
Running this test using Protractor results in the following error:
Error: Timed out waiting for Protractor to synchronize with the page after 11 seconds
However, if in the controller I change this line:
$scope.drafts = Draft.query();
to this:
$scope.drafts = [];
The test fails as expected, but more importantly: it does not time out.
With query() enabled, both when running the app manually in a browser and when looking at the browser window opened by Protractor, the data returned by the API is correctly displayed by a repeater.
Why is Protractor not able to synchronize with the page when the service is communicating with the API?
AngularJS is v1.2.0-rc3. Protractor is v0.12.0.
This is a known issue, but there is a temporary workaround. Set ptor.ignoreSynchronization = true.
For example:
describe('Testing Protractor', function() {
var draftList;
var ptor;
beforeEach(function() {
ptor = protractor.getInstance();
ptor.ignoreSynchronization = true;
});
it('should count the number of drafts', function() {
ptor.get('#/');
draftList = element.all(by.repeater('newsletter in drafts'));
expect(draftList.count()).toEqual(2);
});
});
browser.ignoreSynchronization = true; worked out for me.
I'm using Protractor 3.3.0 and to get this to work in my test I had to defer the ignore synchronisation until after I had done the setup.
So in my beforeEach I call my action:
var searchBox = element(by.css('#inpt_search'));
searchBox.sendKeys('test');
I then have to wait for the mock backend to populate the view (I'm not happy about these sleep calls so if anyone has a better way of doing this please comment, I can't get expectedConditions.presenceOf to work as it's part of the same bug) using browser.sleep(500).
Then in the test I set browser.ignoreSynchronization = true which unblocks whatever is blocked and sees the browser content.
describe('standard search', function (){
beforeEach(function (){
openApp();
var searchBox = element(by.css('#inpt_search'));
searchBox.sendKeys('test');
browser.sleep(500);
});
it('should work or summat', function () {
browser.ignoreSynchronization = true;
var fileItems = element.all(by.repeater('item in list'));
expect(fileItems.count()).toEqual(50);
});
});
Instead of using browser.ignoreSynchronization, use browser.waitForAngularEnabled(*boolean*). browser.waitForAngularEnabled(false) sets browser.ignoreSynchronization to true, browser.waitForAngularEnabled(true) sets browser.ignoreSynchronization to false.
you can also include this as part of your test suites' config file:
onPrepare: function () {
'use strict';
browser.waitForAngularEnabled(false);
}

Categories