describe blocks and do checks in them.expect , should , and assert . In the examples we will use expect .expect(foo).to.be.fulfilled , or expect(foo).to.eventually.equal(bar) . // test/test-helper.js // require('widgetProject'); // require('angular-mocks'); var chai = require('chai'); chai.use('sinon-chai'); chai.use('chai-as-promised'); var sinon = require('sinon'); beforeEach(function() { // this.sinon = sinon.sandbox.create(); }); afterEach(function() { // , this.sinon.restore(); }); module.exports = { rootUrl: 'http://localhost:9000', expect: chai.expect } widgets-project |-test | | | |-e2e | | |-pages | | | |-unit pages folder, we will create a WidgetsPage function that can be enabled in the e2e tests. Five tests refer to it:widgetRepeater : the list of widgets contained in ng-repeatfirstWidget : the first widget in the listwidgetCreateForm : form to create a widgetwidgetCreateNameField : field to enter the name of the widgetwidgetCreateSubmit : form submit button // test/e2e/pages/widgets-page.js var helpers = require('../../test-helper'); function WidgetsPage() { this.get = function() { browser.get(helpers.rootUrl + '/widgets'); } this.widgetRepeater = by.repeater('widget in widgets'); this.firstWidget = element(this.widgetRepeater.row(0)); this.widgetCreateForm = element(by.css('.widget-create-form')); this.widgetCreateNameField = this.widgetCreateForm.element(by.model('widget.name'); this.widgetCreateSubmit = this.widgetCreateForm.element(by.buttonText('Create'); } module.exports = WidgetsPage // e2e/widgets_test.js var helpers = require('../test-helper'); var expect = helpers.expect; var WidgetsPage = require('./pages/widgets-page'); describe('creating widgets', function() { beforeEach(function() { this.page = new WidgetsPage(); this.page.get(); }); it('should create a new widget', function() { expect(this.page.firstWidget).to.be.undefined; expect(this.page.widgetCreateForm.isDisplayed()).to.eventually.be.true; this.page.widgetCreateNameField.sendKeys('New Widget'); this.page.widgetCreateSubmit.click(); expect(this.page.firstWidget.getText()).to.eventually.equal('Name: New Widget'); }); }); expect and WidgetsPage from it. In beforeEach we load into the browser page. Then, in the example, we use elements that are defined in the WidgetsPage to interact with the page. We check that there are no widgets, fill out the form to create one of them with the value “New Widget” and check that it is displayed on the page.isDisplayed and getText return what we expect.$modal service from UI Bootstrap. When a user opens a modal window, the service returns promise. When it cancels or saves the window, the promise is allowed or rejected.save and cancel methods are properly connected by running Chai-as-promised. // widget-editor-service.js var angular = require('angular'); var _ = require('lodash'); angular.module('widgetProject.widgetEditor').service('widgetEditor', ['$modal', '$q', '$templateCache', function ( $modal, $q, $templateCache ) { return function(widgetObject) { var deferred = $q.defer(); var templateId = _.uniqueId('widgetEditorTemplate'); $templateCache.put(templateId, require('./widget-editor-template.html')); var dialog = $modal({ template: templateId }); dialog.$scope.widget = widgetObject; dialog.$scope.save = function() { // - deferred.resolve(); dialog.destroy(); }); dialog.$scope.cancel = function() { deferred.reject(); dialog.destroy(); }); return deferred.promise; }; }]); // test/unit/widget-editor-directive_test.js var angular = require('angular'); var helpers = require('../test_helper'); var expect = helpers.expect; describe('widget storage service', function() { beforeEach(function() { var self = this; self.modal = function() { return { $scope: {}, destroy: self.sinon.stub() } } angular.mock.module('widgetProject.widgetEditor', { $modal: self.modal }); }); it('should persist changes when the user saves', function(done) { var self = this; angular.mock.inject(['widgetModal', '$rootScope', function(widgetModal, $rootScope) { var widget = { name: 'Widget' }; var promise = widgetModal(widget); self.modal.$scope.save(); // expect(self.modal.destroy).to.have.been.called; expect(promise).to.be.fulfilled.and.notify(done); st $rootScope.$digest(); }]); }); it('should not save when the user cancels', function(done) { var self = this; angular.mock.inject(['widgetModal', '$rootScope', function(widgetModal, $rootScope) { var widget = { name: 'Widget' }; var promise = widgetModal(widget); self.modal.$scope.cancel(); expect(self.modal.destroy).to.have.been.called; expect(promise).to.be.rejected.and.notify(done); $rootScope.$digest(); }]); }); }); $modal service in the beforeEach function, replacing the function output with an empty $scope object, and blocking the destroy call. In angular.mock.module , we pass a copy of the modal window so that Angular Mocks can use it instead of the real $modal service. This approach is quite useful for dependency stubs, as we will see soon.done as a parameter to the example ourselves, and done when the test is completed.$rootScope service from AngularJS. Having $rootScope we can call the $digest loop. In each of the tests, we load the modal window, cancel or enable it, and use Chai-as-expected to check, return the promise as rejected or as resolved . To actually call promise and destroy , we need to start $digest , so it is called at the end of each assert block.expect(foo).to.eventually.equal(bar)expect(foo).to.be.fulfilledexpect(foo).to.be.rejecteddestroy was actually called. The technique we used is quite useful and allows unit tests to work more correctly in Angular.var self = this in the beforeEach block.self object: self.dependency = { dependencyMethod: self.sinon.stub() } angular.mock.module('mymodule', { dependency: self.dependecy, otherDependency: self.otherDependency }); expect(foo).to.have.been.called.withArgs , passing in arguments you expect, for better coverage.widgetStorage service and updates the widgets in its environment as the collection changes. There is also an edit method that opens widgetEditor that we created earlier. // widget-viewer-directive.js var angular = require('angular'); angular.module('widgetProject.widgetViewer').directive('widgetViewer', ['widgetStorage', 'widgetEditor', function( widgetStorage, widgetEditor ) { return { restrict: 'E', template: require('./widget-viewer-template.html'), link: function($scope, $element, $attributes) { $scope.$watch(function() { return widgetStorage.notify; }, function(widgets) { $scope.widgets = widgets; }); $scope.edit = function(widget) { widgetEditor(widget); }); } }; }]); widgetStorage and widgetEditor : // test/unit/widget-viewer-directive_test.js var angular = require('angular'); var helpers = require('../test_helper'); var expect = helpers.expect; describe('widget viewer directive', function() { beforeEach(function() { var self = this; self.widgetStorage = { notify: self.sinon.stub() }; self.widgetEditor = self.sinon.stub(); angular.mock.module('widgetProject.widgetViewer', { widgetStorage: self.widgetStorage, widgetEditor: self.widgetEditor }); }); // ... }); $dropdown service from Angular Strap , an isolated scope is created. Getting access to such a scope can be quite painful. But knowing self.element.isolateScope() can fix this. Here is one example of using $dropdown , which creates an isolated scope: // nested-widget-directive.js var angular = require('angular'); angular.module('widgetSidebar.nestedWidget').directive('nestedSidebar', ['$dropdown', 'widgetStorage', 'widgetEditor', function( $dropdown, widgetStorage, widgetEditor ) { return { restrict: 'E', template: require('./widget-sidebar-template.html'), scope: { widget: '=' }, link: function($scope, $element, $attributes) { $scope.actions = [{ text: 'Edit', click: 'edit()' }, { text: 'Delete', click: 'delete()' }] $scope.edit = function() { widgetEditor($scope.widget); }); $scope.delete = function() { widgetStorage.destroy($scope.widget); }); } }; }]); // test/unit/nested-widget-directive_test.js var angular = require('angular'); var helpers = require('../test_helper'); var expect = helpers.expect; describe('nested widget directive', function() { beforeEach(function() { var self = this; self.widgetStorage = { destroy: self.sinon.stub() }; self.widgetEditor = self.sinon.stub(); angular.mock.module('widgetProject.widgetViewer', { widgetStorage: self.widgetStorage, widgetEditor: self.widgetEditor }); angular.mock.inject(['$rootScope', '$compile', '$controller', function($rootScope, $compile, $controller) { self.parentScope = $rootScope.new(); self.childScope = $rootScope.new(); self.compile = function() { self.childScope.widget = { id: 1, name: 'widget1' }; self.parentElement = $compile('<widget-organizer></widget-organizer>')(self.parentScope); self.parentScope.$digest(); self.childElement = angular.element('<nested-widget widget="widget"></nested-widget>'); self.parentElement.append(self.childElement); self.element = $compile(self.childElement)(self.childScope); self.childScope.$digest(); }]); }); self.compile(); self.isolateScope = self.element.isolateScope(); }); it('edits the widget', function() { var self = this; self.isolateScope.edit(); self.rootScope.$digest(); expect(self.widgetEditor).to.have.been.calledWith(self.childScope.widget); }); widgetStorage and widgetEditor , then we start writing the compile function. This function will create two instances of the scope, parentScope and childScope , overwrite the widget and put it in the child scope. Next, compile will set up the scope and complex template: first, compiles the parent widget-organizer , to which the parent scope will be passed. When this is all completed, we will add a nested-widget child element to it, passing the child scope and at the end run $digest .compile function, then climb into the compiled isolated scope of the template (which is the scope from $dropdown ) via self.element.isolateScope() . At the end of the test, we can crawl into the isolated scope for an edit call, and finally verify that the stuck widgetEditor been called with the stuck widget.Source: https://habr.com/ru/post/233705/
All Articles