[英]Angular Unit Test Failing Expected spy
I have below controller to get the books list and single books detail. 我有下面的控制器来获取书单和单书详细信息。 It's working as expected but the unit test is not working as expected.
它按预期工作,但单元测试未按预期工作。
books.controller.js books.controller.js
var myApp = angular.module('myApp');
function BooksController($log, $routeParams, BooksService) {
// we declare as usual, just using the `this` Object instead of `$scope`
const vm = this;
const routeParamId = $routeParams.id;
if (routeParamId) {
BooksService.getBook(routeParamId)
.then(function (data) {
$log.info('==> successfully fetched data for book id:', routeParamId);
vm.book = data;
})
.catch(function (err) {
vm.errorMessage = 'OOPS! Book detail not found';
$log.error('GET BOOK: SOMETHING GOES WRONG', err)
});
}
BooksService.getBooks()
.then(function (data) {
$log.info('==> successfully fetched data');
vm.books = data;
})
.catch(function (err) {
vm.errorMessage = 'OOPS! No books found!';
$log.error('GET BOOK: SOMETHING GOES WRONG', err)
});
}
BooksController.$inject = ['$log', '$routeParams', 'BooksService'];
myApp.controller('BooksController', BooksController);
Spec for above controller in which I want to test the getBook(id) service but somehow I am not able to pass the id of book. 我想在其中测试getBook(id)服务的控制器的规格,但以某种方式我无法传递book的ID。
describe('Get All Books List: getBooks() =>', () => {
const errMsg = 'OOPS! No books found!';
beforeEach(() => {
// injecting rootscope and controller
inject(function (_$rootScope_, _$controller_, _$q_, BooksService) {
$scope = _$rootScope_.$new();
$service = BooksService;
$q = _$q_;
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn($service, 'getBooks').and.returnValue(deferred.promise);
// The injector unwraps the underscores (_) from around the parameter names when matching
$vm = _$controller_('BooksController', {$scope: $scope, $service: BooksService});
});
});
it('should defined getBooks $http methods in booksService', () => {
expect(typeof $service.getBooks).toEqual('function');
});
it('should able to fetch data from getBooks service', () => {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{ id: 1 }, { id: 2 }]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, now we can perform our assertions
expect($vm.books).not.toBe(undefined);
expect($vm.errorMessage).toBe(undefined);
});
it('should print error message if data not fetched', () => {
// Setup the data we wish to return for the .then function in the controller
deferred.reject(errMsg);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, now we can perform our assertions
expect($vm.errorMessage).toBe(errMsg);
});
});
describe('Get Single Book Detail: getBook() =>', () => {
const errMsg = 'OOPS! Book detail not found';
const routeParamId = '59663140b6e5fe676330836c';
beforeEach(() => {
// injecting rootscope and controller
inject(function (_$rootScope_, _$controller_, _$q_, BooksService) {
$scope = _$rootScope_.$new();
$scope.id = routeParamId;
$service = BooksService;
$q = _$q_;
var deferredSuccess = $q.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn($service, 'getBook').and.returnValue(deferredSuccess.promise);
// The injector unwraps the underscores (_) from around the parameter names when matching
$vm = _$controller_('BooksController', {$scope: $scope, $service: BooksService});
});
});
it('should defined getBook $http methods in booksService', () => {
expect(typeof $service.getBook).toEqual('function');
});
it('should print error message', () => {
// Setup the data we wish to return for the .then function in the controller
deferred.reject(errMsg);
// We have to call apply for this to work
$scope.$apply();
// expect($service.getBook(123)).toHaveBeenCalled();
// expect($service.getBook(123)).toHaveBeenCalledWith(routeParamId);
// Since we called apply, now we can perform our assertions
expect($vm.errorMessage).toBe(errMsg);
});
});
"Get Single Book Detail: getBook()" this suit is not working. “获取单书详细信息:getBook()”这套方法不起作用。 Please help me, how to short out this kind of situation.
请帮助我,如何解决这种情况。
Error I am getting is below 我得到的错误如下
Chrome 59.0.3071 (Mac OS X 10.12.5) Books Controller Get Single Book Detail: getBook() => should print error message FAILED
Expected 'OOPS! No books found!' to be 'OOPS! Book detail not found'.
Chrome 59.0.3071 (Mac OS X 10.12.5) Books Controller Get Single Book Detail: getBook() => should print error message FAILED
Expected 'OOPS! No books found!' to be 'OOPS! Book detail not found'.
at Object.it (test/client/controllers/books.controller.spec.js:108:38)
Chrome 59.0.3071 (Mac OS X 10.12.5): Executed 7 of 7 (1 FAILED) (0 secs / 0.068 secs)
.
Chrome 59.0.3071 (Mac OS X 10.12.5): Executed 7 of 7 (1 FAILED) (0.005 secs / 0.068 secs)
EDIT (removed original, 2am answer) 编辑 (移除原件,凌晨2点答复)
Are you using strict
mode? 您是否使用
strict
模式? There appear to be a few scoping issues going on: 似乎存在一些范围界定问题:
deferred
is not declared, making it global implicitly deferred
,使其隐式地变为全局 deferred
promise deferred
承诺 deferredSuccess
is declared with var
making it local to the function passed to inject()
var
声明了deferredSuccess
,使其在传递给inject()
的函数中处于局部状态 deferredSuccess
, you're actually failing the global/list deferred
promise. deferredSuccess
,实际上您未通过global / list的deferred
承诺。 This has no effect, since as mentioned in item 2 that promise was already failed and Q ignores repeated rejections . So, that should explain why the error is not what you think it should be. 因此,这应该可以解释为什么错误不是您认为的应该是的。
deferred
isn't the only variable with scoping issues in your example; 在您的示例中,
deferred
不是唯一存在范围问题的变量; those should be addressed. 这些应该解决。 I suggest wrapping the file in an IFFE and using strict mode .
我建议将文件包装在IFFE中并使用严格模式 。 It'll make the code more predictable and avoid issues like this.
这将使代码更可预测,并避免出现此类问题。
Doing this will only get you halfway there; 这样做只会使您半途而废。 @estus's response should round out the job.
@estus的回应应该使工作更加圆满。
you need to mock $rootScope.
您需要模拟
$rootScope.
with provide. 与提供。
The value of id
is not getting avaibale in controller which is undefined
. id
的值在undefined
控制器中不可用。
So, non-id condition getting executing. 因此,非id条件开始执行。
$scope = _$rootScope_.$new();
$scope.id = routeParamId;
module(function ($provide) {
$provide.value('$rootScope', scope); //mock rootscope with id
});
Real router should never be used in unit tests, with ngRoute
module preferably be excluded from tested modules. 真正的路由器绝对不能用于单元测试中,最好将
ngRoute
模块从测试模块中排除。
$scope.id = routeParamId
is assigned before controller instantiation, but it isn't used at all. $scope.id = routeParamId
在控制器实例化之前分配,但根本没有使用。 Instead, it should be done with mocked $routeParams
. 相反,应该使用
$routeParams
完成$routeParams
。
There's no $service
service. 没有
$service
服务。 It's called BooksService
. 它称为
BooksService
。 Thus getBooks
isn't a spy. 因此,
getBooks
不是间谍。 It's preferable to mock the service completely, not only a single method. 最好完全模拟服务,而不仅仅是单个方法。
mockedBooksService = jasmine.createSpyObj('BooksService', ['getBooks']);
var mockedData1 = {};
var mockedData2 = {};
mockedBooksService.getBooks.and.returnValues(
$q.resolve(mockedData1),
$q.resolve(mockedData2),
);
$vm = $controller('BooksController', {
$scope: $scope,
BooksService: mockedBooksService,
$routeParams: { id: '59663140b6e5fe676330836c' }
});
expect(mockedBooksService.getBooks).toHaveBeenCalledTimes(2);
expect(mockedBooksService.getBooks.calls.allArgs()).toEqual([
['59663140b6e5fe676330836c'], []
]);
$rootScope.$digest();
expect($vm.book).toBe(mockedData2);
// then another test for falsy $routeParams.id
The test reveals the problem in controller code. 该测试在控制器代码中揭示了问题。 Since tested code is called on controller construction,
$controller
should be called every time in it
. 由于测试的代码被称为上控制建设,
$controller
应该被称为在每次it
。 A good way to avoid this is to put initialization code into $onInit
method that could be tested separately. 避免这种情况的一种好方法是将初始化代码放入
$onInit
方法中,该方法可以单独进行测试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.