我正在努力测试一个监视几个变量的控制器。在我的单元测试中,即使调用scope,我也无法调用$ watch函数的回调。$ digest()。看起来这应该很简单,但我没有运气。
这是我在控制器中的内容:
angular.module('app')
.controller('ClassroomsCtrl', function ($scope, Classrooms) {
$scope.subject_list = [];
$scope.$watch('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
$scope.classrooms = Classrooms.search(ctrl.functions.search_params());
}
});
});
这是我的单元测试:
angular.module('MockFactories',[]).
factory('Classrooms', function(){
return jasmine.createSpyObj('ClassroomsStub', [
'get','save','query','remove','delete','search', 'subjects', 'add_subject', 'remove_subject', 'set_subjects'
]);
});
describe('Controller: ClassroomsCtrl', function () {
var scope, Classrooms, controllerFactory, ctrl;
function createController() {
return controllerFactory('ClassroomsCtrl', {
$scope: scope,
Classrooms: Classrooms
});
}
// load the controller's module
beforeEach(module('app'));
beforeEach(module('MockFactories'));
beforeEach(inject(function($controller, $rootScope, _Classrooms_ ){
scope = $rootScope.$new();
Classrooms = _Classrooms_;
controllerFactory = $controller;
ctrl = createController();
}));
describe('Scope: classrooms', function(){
beforeEach(function(){
Classrooms.search.reset();
});
it('should call Classrooms.search when scope.subject_list changes', function(){
scope.$digest();
scope.subject_list.push(1);
scope.$digest();
expect(Classrooms.search).toHaveBeenCalled();
});
});
});
我尝试更换所有范围。$ digest()调用范围。$ apply()调用。我试过给他们打了3到4次,但是我无法收到$ watch的回调。
关于这里可能会发生什么的任何想法?
更新 这是一个更简单的例子,它不会处理模拟,存根或注入工厂。
angular.module('edumatcherApp')
.controller('ClassroomsCtrl', function ($scope) {
$scope.subject_list = [];
$scope.$watch('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
console.log('testing');
$scope.test = 'test';
}
});
单元测试:
it('should set scope.test', function(){
scope.$digest();
scope.subject_list.push(1);
scope.$digest();
expect(scope.test).toBeDefined();
});
这也失败了"预期定义未定义。"并且没有任何内容记录到控制台。
更新2
我注意到了2个更有趣的事情。
似乎有一个问题是,在调用回调时newValue和oldValue是相同的。我将两者记录到控制台,它们都等于[]。因此,例如,如果我将$ watch函数更改为如下所示:
$scope.$watch('subject_list', function(newValue, oldValue){
console.log('testing');
$scope.test = 'test';
});
如果我将$ watch回调函数更改为命名函数,并检查是否曾调用过命名函数,那么也会失败。例如,我可以将控制器更改为:
$scope.update_classrooms = function(newValue, oldValue){
$scope.test = 'testing';
console.log('test');
};
$scope.watch('subject_list', $scope.update_classrooms);
将我的测试改为:
it('should call update_classrooms', function(){
spyOn(scope,'update_classrooms').andCallThrough();
scope.$digest();
scope.subject_list.push(1);
scope.$digest();
expect(scope.update_classrooms).toHaveBeenCalled();
});
它失败了"已经调用了预期的间谍update_classrooms。"
在这种情况下,update_classrooms肯定会被调用,因为' test'登录到控制台。我感到很困惑。
答案 0 :(得分:8)
我刚刚在自己的代码库中遇到了这个问题,答案结果是我需要一个范围。$ digest()在实例化控制器后立即调用。在测试中,您必须在观察变量的每次更改后手动调用范围。$ digest()。这包括在构造控制器以记录初始监视值之后。
此外,正如评论中指定的Vitall一样,集合需要$ watchCollection()。
在简单示例中更改此手表可解决此问题:
$scope.$watch('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
console.log('testing');
$scope.test = 'test';
}
});
到:
$scope.$watchCollection('subject_list', function(newValue, oldValue){
if(newValue !== oldValue) {
console.log('testing');
$scope.test = 'test';
}
});
我做了一个jsfiddle以证明其中的区别:
http://jsfiddle.net/ydv8k4zy/ - 原始测试失败 - 因使用$ watch而失败,而不是$ watchCollection
http://jsfiddle.net/ydv8k4zy/1/ - 功能版
http://jsfiddle.net/ydv8k4zy/2/ - $ watchCollection在没有初始范围的情况下失败。$ digest。
如果您在第二个失败的项目上使用console.log,您将看到在范围之后使用相同的新旧值调用该监视。$ digest()(第25行)。
答案 1 :(得分:2)
这是不可测试的原因是因为函数传递给观察者的方式与spyOn的工作方式相同。 spyOn方法获取范围对象并用新的方法替换该对象上的原始函数...但很可能您通过引用将整个方法传递给$ watch,该手表仍然具有旧方法。我创建了同样的非AngularJS示例:
https://jsfiddle.net/jonhartmann/9bacozmg/
var sounds = {};
sounds.beep = function () {
alert('beep');
};
document.getElementById('x1').addEventListener('click', sounds.beep);
document.getElementById('x2').addEventListener('click', function () {
sounds.beep = function () {
alert('boop');
};
});
document.getElementById('x3').addEventListener('click', function () {
sounds.beep();
});
x1处理程序和x3处理程序之间的区别在于,在第一个绑定中,方法是直接传入的,在第二个中,beep / boop方法只调用对象上的任何方法。
这与我的$ watch遇到的情况相同 - 我将我的方法放在“privateMethods”对象上,将其传递出来并执行spyOn(privateMethods,'methodname'),但由于我的观察者在$ scope的格式。$ watch('value',privateMethods.methodName)为时已晚 - 代码将由我的间谍执行将无法正常工作。切换到这样的事情跳过了问题:
$scope.$watch('value', function () {
privateMethods.methodName.apply(null, Array.prototype.slice.call(arguments));
});
然后是非常期待的
spyOn(privateMethods, 'methodName')
expect(privateMethods.methodName).toHaveBeenCalled();
这是有效的,因为您不再通过引用将privateMethods.methodName传递给$ watch,而是传递一个函数,该函数又在“privateMethods”上执行“methodName”函数。
答案 2 :(得分:0)
您的问题是,您需要拨打$watch
而不是普通$watch
。
scope.subject_list = ['new item']
只会响应作业(即$watchCollection
)。
除了分配,A
将响应列表的更改(推/拼接)。