我使用单元测试来测试我在AngularJS中编写的代码。为此,我使用业力,而karma.conf正在做它所拥有的。
在测试包含表单的指令(使用它自己的控制器)时,我无法访问任何表单的命名输入。这意味着我无法模拟正在填写的表单(使用form.namedproperty。$ setViewValue(val)语法)
以下是我目前使用的代码: directive.html:
<form ng-submit="vm.login(loginForm, vm.user)" name="loginForm" novalidate class="css-form" >
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Aanmelden op HR-App</h4>
</div>
<div class="modal-body">
<pre>{{vm.user | json}}</pre>
<div class="form-group" ng-class="{ 'has-error': loginForm.username.$invalid && (loginForm.username.$dirty || vm.submitted), 'has-success': loginForm.username.$valid}">
<label class="control-label">Windows Login Name</label>
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">@</span>
<input required ng-model="vm.user.username" type="text" name="username" class="form-control" placeholder="e.g. jMetdepet" aria-describedby="basic-addon1">
</div>
<div ng-show="loginForm.username.$invalid && vm.submitted && !vm.error">
<span class="help-block">Vul een geldige username in</span>
</div>
</div>
<div class="form-group" ng-class="{'has-error': loginForm.pass.$invalid && (loginForm.pass.$dirty || vm.submitted), 'has-success' : loginForm.pass.$valid}">
<label class="control-label">Wachtwoord</label>
<input required ng-model="vm.user.pass" type="password" name="pass" class="form-control" placeholder="Wachtwoord">
<div ng-show="loginForm.pass.$invalid && vm.submitted && !vm.error">
<span class="help-block">Vul een geldig wachtwoord in</span>
</div>
</div>
<div ng-show="vm.error">Uw gebruikersnaam of wachtwoord is verkeerd.</div>
<div class="modal-footer">
<button type="submit" class="btn btn-default"><span ng-show="vm.busy" class="glyphicon spinner-icon red"></span> Aanmelden</button>
</div>
</div>
这是我的html为我的指令,我使用templateUrl调用。我使用的表单是loginForm,它有两个命名输入:username&amp;通过。这些是我无法在任何地方找到的输入......
directive.js:
(function () {
"use strict";
angular
.module("app.dashboard")
.directive('myLdapLogin', loginDirective)
.controller('my-ldapLoginController', ctrl);
/*
* This a directive to be used as an element. The directive name is myLdapLogin which compiles into
* my-ldap-login as element.
* This directive is a login form that handles the login independantly from the host(the controller that uses the directive)
* This method allows us to seperate logic into smaller, scalable controllers which are easier to maintain
*/
function loginDirective() {
return {
restrict: 'E',
templateUrl: 'app/components/login/ldaplogin.directive.html',
replace: true, // Replace with the template below
scope: true,
bindToController: {
// selected: '&', //functions
// images: '=', //2 waybinded vars
// id: '@' //one way binded var
},
controller: "my-ldapLoginController",
controllerAs: 'vm',
};
}
;
/*
* This directive has its own controller
* The controller is dependant on the ErrorService en AuthenticationService. Therefore we use dependancy injection
* to call these services (angular factories)
*/
ctrl.$inject = ["AuthenticationService", "ErrorService"];
/*
* @param {Object} AuthenticationService: A service that provides authentication methods
* @param {Object} ErrorService: A service that provides showing error methods
*/
function ctrl(AuthenticationService, ErrorService) {
var vm = this;
vm.submitted = false;
vm.error = false;
vm.user = {};
vm.login = login;
/*
* The invoked method from our form.
* Will call the Authentication service to login and provide feedback to te user.
* The API will return a res in the form off:
* {error:true, message:... code:...} in case it has failed or
* {error:false,data:...} if succesfull
*/
function login(form, model) {
vm.error = false;
vm.submitted = true;
if (form.$valid) {
vm.busy = true;
AuthenticationService.login(model.username, model.pass).then(function (res) {
vm.user = res;
console.log(res);
vm.submitted = false;
vm.busy = false;
form.username.$dirty = false;
form.pass.$dirty = false;
}, function (err) {
vm.busy = false;
//fout 101 = invalid login parameters
if (err.code === 101)
vm.error = true;
else
ErrorService.createError(err.message);
}
);
}
}
}
})();
在我的HTML中,我定义了submit以包含loginForm,以检查它是否在我的指令控制器中有效。我正在使用可以从模块app.dashboard引用的已定义控制器。 这里要注意的是我使用控制器作为语法。这样我摆脱了我的脏范围
继续测试: directive.spec.js
"use strict";
describe('ldaplogin.directive', function () {
var $compile, $rootScope, $httpBackend, el, controller, scope, mockAuthenticationService
var passPromise = false
// Load the app module, which contains the directive
beforeEach(function () {
module('app')
/*
* Create mocking services. We abstract our service to return a desired value to simulate it's real behaviour.
* This leads us to be independant from the actual service and avoid data calls
*/
module(function ($provide) {
$provide.factory('AuthenticationService', ['$q',
function ($q) {
var AuthenticationService = {
login: login
}
/*+
*
* A spy is a stub. the and.callFake will execute the function when the spy is called
*/
var login = jasmine.createSpy('login').and.callFake(function () {
var user = {
name: "j",
type: "hr"
}
if (passPromise) {
return $q.when(user);
} else {
return $q.reject("something went wrong");
}
});
return AuthenticationService;
}]);
$provide.service('util', function () {
//mock util -- this is just an example
});
});
inject(function (_$compile_, _$rootScope_, $injector, _AuthenticationService_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$compile = _$compile_;
$rootScope = _$rootScope_;
mockAuthenticationService = _AuthenticationService_
$httpBackend = $injector.get('$httpBackend');
//Get our directive
el = angular.element("<my-ldap-login></my-ldap-login>")
// Compile a piece of HTML containing the directive
var t=$compile(el)($rootScope);
// fire all the watches, so the scope expression will be evaluated
$rootScope.$digest();
// # Grab controller instance
// This method can be used if the controller is isolated from the global scope. However for my directive I am using a defined controller so it can be fetched by injecting $controller
var controller = el.controller("myLdapLogin")
console.log(controller)
// # Grab scope. Depends on type of scope.
// # See angular.element documentation.
scope = el.isolateScope() || el.scope()
});
});
// Store references to $rootScope and $compile
// so they are available to all tests in this describe block
/*
* Mock our controller
* This syntax is with the controller as
*/
beforeEach(inject(function ($controller) {
controller = $controller('my-ldapLoginController', {
AuthenticationService: mockAuthenticationService
});
}));
it('Replaces the element with the appropriate content', function () {
// Compile a piece of HTML containing the directive
var element = $compile(el)($rootScope);
// fire all the watches, so the scope expression will be evaluated
$rootScope.$digest();
// Check that the compiled element contains the templated content
expect(element.html()).toContain("Uw gebruikersnaam of wachtwoord is verkeerd.");
});
it("should do something to the $rootScope", function () {
// # Grab scope. Depends on type of scope.
// # See angular.element documentation.
console.log(scope)
expect(scope).toBeDefined()
})
it("should resolve login", function () {
/*We can reference the controller as teh variable vm to have the same syntax like we use in the controller (this equals var vm = this in the controller*/
var vm = controller
console.log(vm)
var form = scope.loginForm
console.log(form)
form.username.$setViewValue('j');
form.pass.$setViewValue('j');
$rootScope.$digest()
passPromise = true;
var user
vm.login(form).then(function (res) {
var user = res;
})
$rootScope.$digest()
expect(mockAuthenticationService.login).toHaveBeenCalled()
expect(user).toEqual({name: "j", type: "hr"})
})
});
问题出现在最后一次测试中:在范围内我有一个名为loginForm的对象。此loginForm包含表单的方法,但不包含我的命名输入。 这是我在测试中从作用域获得的loginForm对象:(注意;这是一个循环结构,我不能将JSON字符串化)
Id {$error: Object, $$success: Object, $pending: undefined, $name: "loginForm", $dirty: false…}
$$parentForm:Object
$$renameControl:(a,b)
$$success:Object
$addControl:(a)
$commitViewValue:()
$dirty:false
$error:Object
$invalid:false
$name:"loginForm"
$pending:undefined
$pristine:true
$removeControl:(a)
$rollbackViewValue:()
$setDirty:()
$setPristine:()
$setSubmitted:()
$setUntouched:()
$setValidity:(a,e,f)
$submitted:false
$valid:true
__proto__:Object
如果我使用pre在我的html中记录我的loginForm,我会得到以下对象:
{
"$error": {
"required": [
{
"$validators": {},
"$asyncValidators": {},
"$parsers": [],
"$formatters": [
null
],
"$viewChangeListeners": [],
"$untouched": true,
"$touched": false,
"$pristine": true,
"$dirty": false,
"$valid": false,
"$invalid": true,
"$error": {
"required": true
},
"$name": "username",
"$options": null
},
{
"$validators": {},
"$asyncValidators": {},
"$parsers": [],
"$formatters": [
null
],
"$viewChangeListeners": [],
"$untouched": true,
"$touched": false,
"$pristine": true,
"$dirty": false,
"$valid": false,
"$invalid": true,
"$error": {
"required": true
},
"$name": "pass",
"$options": null
}
]
},
"$name": "loginForm",
"$dirty": false,
"$pristine": true,
"$valid": false,
"$invalid": true,
"$submitted": false,
"username": {
"$validators": {},
"$asyncValidators": {},
"$parsers": [],
"$formatters": [
null
],
"$viewChangeListeners": [],
"$untouched": true,
"$touched": false,
"$pristine": true,
"$dirty": false,
"$valid": false,
"$invalid": true,
"$error": {
"required": true
},
"$name": "username",
"$options": null
},
"pass": {
"$validators": {},
"$asyncValidators": {},
"$parsers": [],
"$formatters": [
null
],
"$viewChangeListeners": [],
"$untouched": true,
"$touched": false,
"$pristine": true,
"$dirty": false,
"$valid": false,
"$invalid": true,
"$error": {
"required": true
},
"$name": "pass",
"$options": null
}
}
在这个对象上,我将命名输入作为属性,但在我的测试范围内,在我的测试范围内,我没有。
有人知道如何将这些输入纳入我的范围内的表格吗?
我试图设置一个plunker但我似乎无法解决你可以使用ng-html2js修复的templateUrl问题