I have a angular service that wraps a server-sent events. How do I mock the server-sent events in unit testing? Can I use mockHttpbackend in this case?
My hack:
create a MockEventSource
class MockEventSource {
static lastInstance: MockEventSource;
constructor(url: string) {
MockEventSource.lastInstance = this;
}
onmessage: (message: any) => any;
}
register it on the window object
beforeEach(() => {
window["EventSource"] = MockEventSource;
MockEventSource.lastInstance = null;
});
and call onmessage on it:
MockEventSource.lastInstance.onmessage({ data: JSON.stringify(...) });
Related
I've ran into issues in the past wherein SocketIO will emit duplicate events when using AngularJS. There are two common reasons as to why this occurs:
Creating your event listeners inside the connect event listener.
Event listeners being dynamically created multiple times (usual case with AngularJS).
Luckily, there are two common solutions that match with the above scenarios. Please see the accepted answer to understand these solutions.
Creating your event listeners inside the connect event listener
This bug occurs if you create listeners inside your connect event listener. Simply move these listeners outside the connect event listener.
Before:
socket.on('connection', _ => {
console.log('Connected');
socket.on('someOtherEvent', data => {
console.log(data);
});
});
After:
socket.on('connection', _ => {
console.log('Connected');
});
socket.on('someOtherEvent', data => {
console.log(data);
});
Event listeners being dynamically created multiple times (usual case with AngularJS).
This is a common problem if you register your event listeners inside a controller. To fix this, edit your service like so:
angular.module('socketIO', []).factory('socket', $rootScope => {
class SocketHandler {
constructor(url) {
this._socket = false;
this._url = url;
this._events = {};
this._build();
}
_build() {
// Change for your configuration options below.
this._socket = io.connect(this._URL);
}
on(event, callback) {
if(this._events.hasOwnProperty(event))
return this._events[event];
return this._events[event] = this._socket.on(event, (...args) => {
$rootScope.$apply(_ => {
callback.apply(this._socket, args);
});
});
}
emit(event, data, callback = false) {
return this._socket.emit(event, data, (...args) => {
if(!callback)
return;
$rootScope.$apply(_ => {
callback.apply(this._socket, args);
});
});
}
}
return { build: SocketHandler };
});
You can then use this in your constructor:
angular.module('someController', socketIO => {
const socket = socketIO.build('your socket url');
});
The reason this fix works is because when you switch pages in AngularJS (with the default router) it recreates an instance of the controller. This means that multiple event listeners are now bound to a single event. We prevent this from occurring by caching our event listeners in SocketHandler._events, and returning the cached listener if we have it cached.
I am writing an app using angular 1.6, typescript, webpack, karma and jasmine. I was able to create unit tests for angular services, but now I am having troubles for testing components. On SO(1) and (2) and on the net I found different examples (like this), but not a clear guide explaining how to test angular 1 components with the above technology set.
My component (HeaderComponent.ts):
import {IWeatherforecast} from '../models/weather-forecast';
import WeatherSearchService from '../search/weather-search.service';
import WeatherMapperService from '../common/mapping/weatherMapper.service';
export default class HeaderComponent implements ng.IComponentOptions {
public bindings: any;
public controller: any;
public controllerAs: string = 'vm';
public templateUrl: string;
public transclude: boolean = false;
constructor() {
this.bindings = {
};
this.controller = HeaderComponentController;
this.templateUrl = 'src/header/header.html';
}
}
export class HeaderComponentController {
public searchText:string
private weatherData : IWeatherforecast;
static $inject: Array<string> = ['weatherSearchService',
'$rootScope',
'weatherMapperService'];
constructor(private weatherSearchService: WeatherSearchService,
private $rootScope: ng.IRootScopeService,
private weatherMapperService: WeatherMapperService) {
}
public $onInit = () => {
this.searchText = '';
}
public searchCity = (searchName: string) : void => {
this.weatherSearchService.getWeatherForecast(searchName)
.then((weatherData : ng.IHttpPromiseCallbackArg<IWeatherforecast>) => {
let mappedData = this.weatherMapperService.ConvertSingleWeatherForecastToDto(weatherData.data);
sessionStorage.setItem('currentCityWeather', JSON.stringify(mappedData));
this.$rootScope.$broadcast('weatherDataFetched', mappedData);
})
.catch((error:any) => console.error('An error occurred: ' + JSON.stringify(error)));
}
}
The unit test:
import * as angular from 'angular';
import 'angular-mocks';
import HeaderComponent from '../../../src/header/header.component';
describe('Header Component', () => {
let $compile: ng.ICompileService;
let scope: ng.IRootScopeService;
let element: ng.IAugmentedJQuery;
beforeEach(angular.mock.module('weather'));
beforeEach(angular.mock.inject(function (_$compile_: ng.ICompileService, _$rootScope_: ng.IRootScopeService) {
$compile = _$compile_;
scope = _$rootScope_;
}));
beforeEach(() => {
element = $compile('<header-weather></header-weather>')(scope);
scope.$digest();
});
To me is not clear how to access the controller class, in order to test the component business logic. I tried injecting $componentController, but i keep getting the error "Uncaught TypeError: Cannot set property 'mock' of undefined", I think this is related to angular-mocks not properly injected.
Can anyone suggest an approach of solution or a site where to find further details about unit testing angular 1 components with typescript and webpack?
I was able to found a solution for my question. I post the edited code below, so others can benefit from it and compare the starting point (the question above) with the final code for the unit test(below, splitted in sections for the sake of explanation).
Test the component template :
import * as angular from 'angular';
import 'angular-mocks/angular-mocks';
import weatherModule from '../../../src/app/app.module';
import HeaderComponent, { HeaderComponentController } from '../../../src/header/header.component';
import WeatherSearchService from '../../../src/search/weather-search.service';
import WeatherMapper from '../../../src/common/mapping/weatherMapper.service';
describe('Header Component', () => {
let $rootScope: ng.IRootScopeService;
let compiledElement: any;
beforeEach(angular.mock.module(weatherModule));
beforeEach(angular.mock.module('templates'));
beforeEach(angular.mock.inject(($compile: ng.ICompileService,
_$rootScope_: ng.IRootScopeService) => {
$rootScope = _$rootScope_.$new();
let element = angular.element('<header-weather></header-weather>');
compiledElement = $compile(element)($rootScope)[0];
$rootScope.$digest();
}));
As for directives, also for components we need to compile the relative template and trigger a digest loop.
After this step, we can test the generated template code:
describe('WHEN the template is compiled', () => {
it('THEN the info label text should be displayed.', () => {
expect(compiledElement).toBeDefined();
let expectedLabelText = 'Here the text you want to test';
let targetLabel = angular.element(compiledElement.querySelector('.label-test'));
expect(targetLabel).toBeDefined();
expect(targetLabel.text()).toBe(expectedLabelText);
});
});
Test the component controller :
I created two mocked objects with jasmine.createSpyObj. In this way it is possible to create an instance of our controller and pass the mocked objects with the desired methods.
As the mocked method in my case was returning a promise, we need to use the callFake method from the jasmine.SpyAnd namespace and return a resolved promise.
describe('WHEN searchCity function is called', () => {
let searchMock: any;
let mapperMock: any;
let mockedExternalWeatherData: any;
beforeEach(() => {
searchMock = jasmine.createSpyObj('SearchServiceMock', ['getWeatherForecast']);
mapperMock = jasmine.createSpyObj('WeatherMapperMock', ['convertSingleWeatherForecastToDto']);
mockedExternalWeatherData = {}; //Here I pass a mocked POCO entity (removed for sake of clarity)
});
it('WITH proper city name THEN the search method should be invoked.', angular.mock.inject((_$q_: any) => {
//Arrange
let $q = _$q_;
let citySearchString = 'Roma';
searchMock.getWeatherForecast.and.callFake(() => $q.when(mockedExternalWeatherData));
mapperMock.convertSingleWeatherForecastToDto.and.callFake(() => $q.when(mockedExternalWeatherData));
let headerCtrl = new HeaderComponentController(searchMock, $rootScope, mapperMock);
//Act
headerCtrl.searchCity(citySearchString);
//Assert
expect(searchMock.getWeatherForecast).toHaveBeenCalledWith(citySearchString);
}));
});
});
Thanks for this post! I worked at the same time at the same problem and also found a solution. But this hero example doesn't require compiling the component (also no digest required) but uses the $componentController where also bindings can be defined.
The my-components module - my-components.module.ts:
import {IModule, module, ILogService} from 'angular';
import 'angular-material';
export let myComponents: IModule = module('my-components', ['ngMaterial']);
myComponents.run(function ($log: ILogService) {
'ngInject';
$log.debug('[my-components] module');
});
The hero component - my-hero.component.ts
import {myComponents} from './my-components.module';
import IController = angular.IController;
export default class MyHeroController implements IController {
public hero: string;
constructor() {
'ngInject';
}
}
myComponents.component('hero', {
template: `<span>Hero: {{$ctrl.hero}}</span>`,
controller: MyHeroController,
bindings: {
hero: '='
}
});
The hero spec file - my-hero.component.spec.ts
import MyHeroController from './my-hero.component';
import * as angular from 'angular';
import 'angular-mocks';
describe('Hero', function() {
let $componentController: any;
let createController: Function;
beforeEach(function() {
angular.mock.module('my-components');
angular.mock.inject(function(_$componentController_: any) {
$componentController = _$componentController_;
});
});
it('should expose a hero object', function() {
let bindings: any = {hero: 'Wolverine'};
let ctrl: any = $componentController('hero', null, bindings);
expect(ctrl.hero).toBe('Wolverine');
})
});
Note: It took some time to fix an error in testing the binding:
$compileProvider doesn't have method 'preAssignBindingsEnabled'
The reason was a version difference between angular and angular-mock. The solution was provide by: Ng-mock: $compileProvider doesn't have method 'preAssignBindingsEnabled`
I'm currently trying to convert some good old $http Promises into rxJs observables in angular2.
A very simple example of something I used to do frequently in angular 1:
// Some Factory in Angular 1
var somethings = [];
var service = {
all: all
};
return service;
function all() {
return $http
.get('api/somethings')
.then(function(response) {
somethings = parseSomethings(response.data);
return somethings;
});
}
// Some Controller in Angular 1
var vm = this;
vm.somethings = [];
vm.loading = true;
loadThings();
function loadThings() {
SomeFactory
.all()
.then(function(somethings) {
vm.somethings = somethings;
})
.finally(function() {
vm.loading = false;
})
}
Now I can achieve something similar with angular2 using RxJs Observable and Subject. But I am unsure on how to return 'completion hooks' down to the caller. (E.g.: The loading in the angular1 controller)
What I tried with for angular2 is something similar:
// Some Service in Angular 2
export class SomeService {
private _somethings$: Subject<Something[]>;
private dataStore: {
somethings: Something[]
};
constructor(private http: Http) {}
get _somethings$() : Observable<Something[]> {
return this._somethings$.asObservable();
}
loadAll() {
this.http
.get(`api/somethings`)
.map(response => response.json())
.subscribe(
response => {
this.dataStore.somethings = parseSomethings(response.data);
this._somethings$.next(this.dataStore.somethings);
}
);
}
}
//Some Component in Angular 2
export class SomeComponent() {
constructor(private someService: SomeService) {}
ngOnInit() {
this.someService.loadAll();
this.somethings = this.someService.somethings$;
}
}
Note
I used a Subject / Observable here so that when I create / update / remove 'something' I can call .next to notify the subscribers.
E.g.:
create(something: Something) : Observable<Something>{
return this.http.post(`api/somethings`, JSON.stringify(something))
.map(response => response.json())
.do(
something => {
this.dataStore.somethings.push(something);
this._somethings$.next(this.dataStore.somethings); //Notify the subscribers
}
);
}
Note
Here I used .do in the method and when calling the method in a 'form component' I subscribe just to get completion handler:
this.someService.create(something).subscribe(
(ok) => ...
(error) => ...
() => stop loading indicator
)
Questions
How can we pass down a completion-hook to callers (e.g. angular 1 controller) in angular 2 with RxJs
What alternative paths do exist to achieve similar behaviour ?
Your last code example is fine. Just use map() instead of subscribe() and subscribe at call site.
You can use
return http.post(...).toPromise(val => ...))
or
return http.post(...).map(...).toPromise(val => ...))
to get the same behavior as in Angular where you can chain subsequent calls with .then(...)
I'm trying to test a component with controller, with some bindings:
class AppSpecificController {
constructor() {
this.text = this.initialText ? this.initialText : 'No initial text has been specified.';
}
}
export const AppSpecificComponent = {
bindings: {
'initialText': '<bindInitialText'
},
templateUrl: '/app/components/app-specific/app-specific.html',
controller: AppSpecificController,
controllerAs: 'appSpecific'
};
export default AppSpecificComponent;
In my unit test file I don't want to load the complete application, just the things I need. So I figured to mock a module or just create a new one named something with mock, add the component to that module, and load that module:
import {AppSpecificComponent} from './app-specific.component';
describe('AppSpecificComponent', () => {
let controller;
let scope;
let $componentController;
beforeEach(() => {
angular.module('mock-module', [])
.component('appSpecific', AppSpecificComponent);
// this fails
module('mock-module');
inject((_$componentController_, $rootScope) => {
scope = $rootScope.$new();
$componentController = _$componentController_;
});
controller = $componentController('appSpecific', {$scope: scope}, {initialText: 'Some text'});
});
it('should test something', () => {
expect(true).toBeTruthy();
});
});
Creating the module mock-module is fine, but loading it fails, and obviously injecting stuff in the not-so-much-loaded-module fails, as well as creating a controller on which I can start testing. It would be nice to be able to test the components individually, separate from the application in which it runs.
Just using the new operator for the class AppSpecificController doesn't work since then the bindings you receive from the component are not there:
// fails, no bindings available in controller
controller = new AppSpecificController();
I found the answer somewhere else on StackOverflow, not sure where anymore. The answer however I was looking for:
angular.mock.module($provide => {
$provide.controller('SomeController', () => { ... });
$provide.constant('someConstant', 'some constant');
});
I have to implement some standard notification UI with angular js. My approach is the following (simplified):
<div ng-controller="MainCtrl">
<div>{{message}}</div>
<div ng-controller="PageCtrl">
<div ng-click="showMessage()"></div>
</div>
</div>
And with the page controller being:
module.controller("PageCtrl", function($scope){
counter = 1
$scope.showMessage = function(){
$scope.$parent.message = "new message #" + counter++;
};
});
This works fine. But I really don't like the fact that I need to call $scope.$parent.
Because if I am in another nested controller, I will have $scope.$parent.$parent, and this becomes quickly a nightmare to understand.
Is there another way to create this kind of global UI notification with angular?
Use events to send messages from one component to another. That way the components don't need to be related at all.
Send an event from one component:
app.controller('DivCtrl', function($scope, $rootScope) {
$scope.doSend = function(){
$rootScope.$broadcast('divButton:clicked', 'hello world via event');
}
});
and create a listener anywhere you like, e.g. in another component:
app.controller('MainCtrl', function($scope, $rootScope) {
$scope.$on('divButton:clicked', function(event, message){
alert(message);
})
});
I've created a working example for you at http://plnkr.co/edit/ywnwWXQtkKOCYNeMf0FJ?p=preview
You can also check the AngularJS docs about scopes to read more about the actual syntax.
This provides you with a clean and fast solution in just a few lines of code.
Regards,
Jurgen
You should check this:
An AngularJS component for easily creating notifications. Can also use HTML5 notifications.
https://github.com/phxdatasec/angular-notifications
After looking at this: What's the correct way to communicate between controllers in AngularJS? and then that: https://gist.github.com/floatingmonkey/3384419
I decided to use pubsub, here is my implementation:
Coffeescript:
module.factory "PubSub", ->
cache = {}
subscribe = (topic, callback) ->
cache[topic] = [] unless cache[topic]
cache[topic].push callback
[ topic, callback ]
unsubscribe = (topic, callback) ->
if cache[topic]
callbackCount = cache[topic].length
while callbackCount--
if cache[topic][callbackCount] is callback
cache[topic].splice callbackCount, 1
null
publish = (topic) ->
event = cache[topic]
if event and event.length>0
callbackCount = event.length
while callbackCount--
if event[callbackCount]
res = event[callbackCount].apply {}, Array.prototype.slice.call(arguments, 1)
# some pubsub enhancement: we can get notified when everything
# has been published by registering to topic+"_done"
publish topic+"_done"
res
subscribe: subscribe
unsubscribe: unsubscribe
publish: publish
Javascript:
module.factory("PubSub", function() {
var cache, publish, subscribe, unsubscribe;
cache = {};
subscribe = function(topic, callback) {
if (!cache[topic]) {
cache[topic] = [];
}
cache[topic].push(callback);
return [topic, callback];
};
unsubscribe = function(topic, callback) {
var callbackCount;
if (cache[topic]) {
callbackCount = cache[topic].length;
while (callbackCount--) {
if (cache[topic][callbackCount] === callback) {
cache[topic].splice(callbackCount, 1);
}
}
}
return null;
};
publish = function(topic) {
var callbackCount, event, res;
event = cache[topic];
if (event && event.length > 0) {
callbackCount = event.length;
while (callbackCount--) {
if (event[callbackCount]) {
res = event[callbackCount].apply({}, Array.prototype.slice.call(arguments, 1));
}
}
publish(topic + "_done");
return res;
}
};
return {
subscribe: subscribe,
unsubscribe: unsubscribe,
publish: publish
};
});
My suggestion is don't create a one on your own. Use existing models like toastr or something like below.
http://beletsky.net/ng-notifications-bar/
As suggested above, try to use external notifications library. There're a big variety of them:
http://alertifyjs.com/
https://notifyjs.com/
https://www.npmjs.com/package/awesome-notifications
http://codeseven.github.io/toastr/demo.html