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-repeat
firstWidget
: 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.fulfilled
expect(foo).to.be.rejected
destroy
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