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

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.

Related

Angular 1.5 component/require dependency injection of $log

I have been trying to inject $log in to a component created by a require statement for some client Angular.
var App = require('./app/containers/App');
var Header = require('./app/components/Header');
require('angular-ui-router');
var routesConfig = require('./routes');
import './index.css';
angular
.module('app', ['ui.router'])
.config(routesConfig)
.controller(App.App, ['$log'])
.service('todoService', todos.TodoService)
.component('app', App)
.component('headerComponent', Header);
The code for header is
module.exports = {
template: require('./Header.html'),
controller: Header,
bindings: {
todos: '='
}
};
/** #ngInject */
function Header(todoService) {
this.todoService = todoService;
}
Header.prototype = {
handleSave: function (text) {
if (text.length !== 0) {
this.todos = this.todoService.addTodo(text, this.todos);
}
}
};
~
The code for App is
module.exports = {
template: require('./App.html'),
controller: App
};
function App($log) {
this.log = $log;
$log.error('Hello from App');
}
I can inject $log as dependency for App as I have access to the controller. But attempting the same task for Header is difficult,because Header is created by require which does not seem to allow access to the controller function.
What I like to know is there a way round this?
I am trying to find a way of logging information from any possible javascript function in header.js.
I have seen any alternatives other than using $log to log information in a client side application
My solution so far has been to say in code written in the require block.
var ing = angular.injector(['ng']);
this.$log = ing.get('$log');
this.$log.error('I am a message');
I think this is the wrong way of doing things, it gives me what I want, but I expect it will break at some point. I find having access to $log is useful for debugging only. Its not sort of thing I need for any production code.
All I was trying to do was to get access to the $log angular wrapper. Turns out all I had to do was add $log to the argument list.
function Header(todoService,$log) {
$log.log('I am a log message');
this.todoService = todoService;
}
I am bit of a Angular 1.5 newbie and I had assume that you had to inject the $log to get the right response. Just declare it seems to be a bit of a kop out.

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.

How to mock promise with data from JSON fixture

I'm testing my Angular controller using mock data pulled in from a JSON file. The problem I'm running into is that my controller expects a promise, whereas my mock data is simply a JSON object.
Relevant controller code:
var vm = this;
var playerId = $routeParams.playerId;
playersService.getInfo({
playerId: playerId
}).$promise.then(function(info) {
vm.info = info;
});
Relevant test code:
$httpBackend = $injector.get('$httpBackend');
jasmine.getJSONFixtures().fixturesPath = 'base/test/fixtures';
$httpBackend.whenGET('/api/players/5/info').respond(
getJSONFixture('info.json')
);
I'm trying to work out how to setup my test to work with the controller's promise structure.

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

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);

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