【发布时间】:2023-04-01 22:53:01
【问题描述】:
我正在为 Treehouse Full Stack JavaScript TechDegree 开发带有 AngularJS 的单页应用程序项目,并且我正在尝试对控制器进行单元测试。要测试对 dataService 进行 api 调用的控制器,我必须模拟 dataService,但我无法弄清楚如何正确执行此操作。我已经阅读了一篇又一篇关于 Angular 单元测试的文章,但我很迷茫,不知道下一步该做什么。
controllers.js:
(function() {
'use strict';
angular.module('app')
.controller('RecipesController', function(dataService,$location) {
const vm = this;
vm.init = () => {
vm.hidden = true;
dataService.getAllRecipes(function(response) {
vm.recipes = response.data;
vm.getCategories(response.data);
});
}
vm.selectCategory = (category) => {
if (category === null) {
vm.init();
} else {
dataService.getCategory(category,function(response) {
vm.recipes = response.data;
});
}
};
vm.getCategories = (data) => {
let categories = new Set();
for (let item of data) {
categories.add(item.category);
}
vm.categories = Array.from(categories);
};
vm.addRecipe = () => {
$location.path('/add');
}
vm.deleteRecipe = (recipe,$index) => {
vm.toDelete = recipe.name;
vm.hidden = false;
vm.deleteIt = () => {
vm.hidden = true;
dataService.deleteRecipe(recipe._id,function(response) {
vm.init();
});
}
}
vm.init();
})
.controller('RecipeDetailController', function($scope,dataService,$location) {
const vm = this;
const init = () => {
const path = $location.path();
if (path.includes("edit")) {
let id = path.slice(6);
dataService.getID(id,function(response) {
vm.recipe = response.data;
vm.title = response.data.name;
vm.editCategory = response.data.category;
});
} else if (path.includes("add")) {
vm.recipe = {
name: "",
description: "",
category: "",
prepTime: 0,
cookTime: 0,
ingredients: [
{
foodItem: "",
condition: "",
amount: ""
}
],
steps: [
{
description: ""
}
]
}
vm.title = 'Add New Recipe.'
}
dataService.getAllCategories(function (response) {
vm.categories = response.data;
let index = response.data.findIndex(item => item.name === $scope.editCategory);
if (index === -1) {
vm.initial = {"name": "Choose a Category"};
} else {
vm.initial = $scope.categories[index];
}
});
dataService.getAllFoodItems(function (response) {
vm.foods = response.data;
});
}
vm.addItem = (item) => {
if (item === 'ingredient') {
vm.recipe.ingredients.push({amount: "amount", condition: "condition", foodItem: ""});
} else if (item === 'step') {
vm.recipe.steps.push({description: "description"});
}
};
vm.deleteItem = (item,$index) => {
if (item === 'ingredient') {
vm.recipe.ingredients.splice($index,1);
} else if (item === 'step') {
vm.recipe.steps.splice($index,1);
}
}
vm.saveChanges = (recipe) => {
vm.errors = [];
const buildErrorArray = (errorArray) => {
for (let item of errorArray) {
vm.errors.push(item.userMessage);
}
}
const collectErrors = (response) => {
if (response.data.errors.category) { buildErrorArray(response.data.errors.category) }
if (response.data.errors.ingredients) { buildErrorArray(response.data.errors.ingredients) }
if (response.data.errors.name) { buildErrorArray(response.data.errors.name) }
if (response.data.errors.steps) { buildErrorArray(response.data.errors.steps) }
}
if (recipe._id) {
dataService.updateID(recipe,function(response) {
$location.path('/');
}, function(response) {
collectErrors(response)
});
} else {
dataService.addRecipe(recipe,function(response) {
$location.path('/');
}, function(response) {
collectErrors(response)
});
}
}
vm.cancelChanges = () => {
$location.path('/');
}
init();
});
}());
services.js:
(function() {
'use strict';
angular.module('app')
.service('dataService', function($http,errors,httpErrors) {
this.getAllRecipes = function (callback) {
$http.get('http://localhost:5000/api/recipes')
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getAllCategories = function (callback) {
$http.get('http://localhost:5000/api/categories')
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getAllFoodItems = function (callback) {
$http.get('http://localhost:5000/api/fooditems')
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getCategory = function(category,callback) {
$http.get('http://localhost:5000/api/recipes?category=' + category)
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getID = function (id,callback) {
$http.get('http://localhost:5000/api/recipes/' + id)
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.updateID = function (data,success,error) {
$http.put('http://localhost:5000/api/recipes/' + data._id, data)
.then(success,error).catch(errors.catch());
};
this.addRecipe = function (data,success,error) {
$http.post('http://localhost:5000/api/recipes', data)
.then(success,error).catch(errors.catch());
};
this.deleteRecipe = function (id,callback) {
$http.delete('http://localhost:5000/api/recipes/' + id)
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
});
}());
controllersSpec.js:
describe("Unit Testing Controllers", function() {
beforeEach(angular.mock.module('app'));
let $scope;
let getAllRecipesMock;
beforeEach(inject(function(_$controller_,_$rootScope_,$q) {
$controller = _$controller_;
$scope = _$rootScope_.$new();
getAllRecipesMock = {
getAllRecipes: function() {
var deferred = $q.defer();
deferred.resolve([{name: "recipename"}]);
return deferred.promise;
}
}
}));
it('has a test to test that tests are testing', function() {
expect(2 + 2).toEqual(4);
});
it('should have a RecipesController', function() {
const controller = $controller('RecipesController',{$scope:$scope});
expect(controller).toBeDefined();
});
it('should have a RecipeDetailController', function() {
const controller = $controller('RecipeDetailController',{$scope:$scope});
expect(controller).toBeDefined();
});
it('should call the getAllRecipes service and return response', inject(function() {
const controller = $controller('RecipesController',{$scope:$scope,dataService:getAllRecipesMock});
$scope.$digest();
expect(controller.recipes).toBe([{name: "recipename"}]);
}));
it('should remove duplicate categories', function() {
const controller = $controller('RecipesController',{$scope:$scope});
let data = [{'category':'dog'},{'category':'cat'},{'category':'horse'},{'category':'dog'},{'category':'cow'}];
controller.getCategories(data);
expect(controller.categories).toEqual(['dog','cat','horse','cow']);
});
it('should take you to the /add route when the addRecipe method is called', inject(function($location) {
const controller = $controller('RecipesController',{$scope:$scope});
controller.addRecipe();
expect($location.path()).toEqual('/add');
}));
});
这是我运行测试时得到的结果:
Unit Testing Controllers
√has a test to test that tests are testing
√should have a RecipesController
√should have a RecipeDetailController
×should call the getAllRecipes service and return response
Expected undefined to be [ Object({ name: 'recipename' }) ].
at Object.<anonymous> (test/controllersSpec.js:38:32)
at Object.invoke (node_modules/angular/angular.js:4839:19)
at Object.WorkFn (node_modules/angular-mocks/angular-mocks.js:3155:20)
√should remove duplicate categories
√should take you to the /add route when the addRecipe method is called
Chrome 55.0.2883 (Windows 10 0.0.0): Executed 6 of 6 (1 FAILED) (0.235 secs / 0.084 secs)
TOTAL: 1 FAILED, 5 SUCCESS
1) should call the getAllRecipes service and return response
Unit Testing Controllers
Expected undefined to be [ Object({ name: 'recipename' }) ].
at Object.<anonymous> (test/controllersSpec.js:38:32)
at Object.invoke (node_modules/angular/angular.js:4839:19)
at Object.WorkFn (node_modules/angular-mocks/angular-mocks.js:3155:20)
编辑
我决定更改服务以返回承诺而不是回调:
this.getAllRecipes = function () {
return $http.get('http://localhost:5000/api/recipes');
};
然后我在控制器中更改了相应的功能:
vm.init = () => {
vm.hidden = true;
let allRecipes = dataService.getAllRecipes();
allRecipes.then(function(response) {
vm.recipes = response.data;
vm.getCategories(response.data);
},httpErrors.display('HTTP Error'))
.catch(errors.catch());
}
但我还是得到了
Expected undefined to be [ Object({ name: 'recipename' }) ].
我没有正确执行承诺吗?我的测试中是否还有一些我遗漏的东西?
【问题讨论】:
标签: javascript angularjs unit-testing