karma jasmine

我们的目标 ( Our Goal )

In this tutorial we will be creating and testing the user profile page for the employee directory we started building in Part 1 of this tutorial. The user profile page will show the details of each employee. Due to the recent comeback of Pokémon, thanks to Pokémon Go, our employee's have requested that their profile pages display an image of their favorite Pokémon as well. Thankfully for us, this provides us the opportunity to write tests for hitting a real API. We'll also write our own custom filter for our user profile page and test the filter as well. By the end of this tutorial you will have the ability to view user profiles that make HTTP requests to Pokéapi.

在本教程中,我们将为我们在本教程的第1部分中开始构建的员工目录创建和测试用户个人资料页面。 用户个人资料页面将显示每个员工的详细信息。 由于Pokémon最近卷土重来,由于PokémonGo,我们的员工要求他们的个人资料页也显示其最喜欢的Pokémon的图像。 值得庆幸的是,这为我们提供了编写测试真实API的机会。 我们还将为我们的用户个人资料页面编写自己的自定义过滤器,并测试该过滤器。 在本教程结束时,您将能够查看对Pokéapi发出HTTP请求的用户配置文件。

你应该知道什么 ( What You Should Know )

Like the previous tutorial, this one will be focused on testing our controllers, factories, and filters that will be used for our user profile page so my assumption is that you're comfortable working with JavaScript and AngularJS applications. We'll be continuing with the application that was created in the first part of this tutorial so if you haven't worked your way through that yet, I'd recommend completing that first or cloning this repository which is the end result of the tutorial.

像上一教程一样,本教程将集中于测试将用于我们的用户个人资料页面的控制器,工厂和过滤器,因此我认为您可以轻松使用JavaScript和AngularJS应用程序。 我们将继续本教程第一部分中创建的应用程序,因此,如果您还没有完成此工作,建议您先完成该步骤或克隆该存储库 ,这是本教程的最终结果。 。

测试角度控制器 ( Testing an Angular Controller )

In Part 1 of this tutorial, we created and tested a Users service but it isn't being used in our application just yet. Let's create a controller and view to display all of our users and write a test for this controller as well. Within the app directory of our application let's create a new components directory. Within this, create a users directory which will contain our view template, controller, and test file for our controller.

在本教程的第1部分中 ,我们创建并测试了Users服务,但目前尚未在我们的应用程序中使用它。 让我们创建一个控制器并查看以显示所有用户,并为该控制器编写一个测试。 在我们应用程序的app目录中,我们创建一个新的components目录。 在其中,创建一个users目录,其中将包含我们的视图模板,控制器和控制器的测试文件。

cd app
mkdir components && cd components
mkdir users && cd users
touch users.js users.spec.js users.html 

At this point your project structure should now look like this:

此时,您的项目结构现在应如下所示:

|-meet-irl|-app      |-components
        |-users
          |-users.js
          |-users.spec.js
          |-users.html
    |-services
      |-users
        |-users.js
        |-users.spec.js
    |-app.css
    |-app.js
    |-index.html|-karma.conf.js|-server.js

Our expectation for this view is that it will display all of our users that we defined in our Users service. So within the controller we're going to make a call to Users.all and set that to our controller's view-model object. From there we can use the ng-repeat directive to iterate over that list of users and display it in our view.

我们期望该视图将显示我们在“ Users服务中定义的所有Users 。 因此,在控制器内,我们将调用Users.all并将其设置为控制器的view-model对象。 从那里,我们可以使用ng-repeat指令遍历该用户列表,并将其显示在我们的视图中。

Before we test that functionality, let's first write a basic test for the existence of this controller in components/users/users.spec.js.

在测试该功能之前,让我们首先在components/users/users.spec.js编写一个基本的测试,以测试该控制器的存在。

describe('UsersController', function() {var $controller, UsersController;// Load ui.router and our components.users module which we'll create nextbeforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('components.users'));// Inject the $controller service to create instances of the controller (UsersController) we want to testbeforeEach(inject(function(_$controller_) {$controller = _$controller_;UsersController = $controller('UsersController', {});}));// Verify our controller existsit('should be defined', function() {expect(UsersController).toBeDefined();});
});

First we create two variables: $controller and UsersController. $controller will be set to Angular's built-in controller service and UsersController will be set to the actual instance of our controller we will write.

首先,我们创建两个变量: $controllerUsersController$controller将设置为Angular的内置控制器服务,而UsersController将设置为我们将编写的控制器的实际实例。

After that, we use angular-mocks to specify which modules we'll need within this test file. In this case we'll need ui.router and components.users which we'll create to make this test pass. The need for ui.router will be seen shortly when we create our controller since we specify all of its state options within the same file.

之后,我们使用angular-mocks来指定此测试文件中需要的模块。 在这种情况下,我们将需要创建ui.routercomponents.users来通过测试。 创建控制器后,很快就会看到对ui.router的需求,因为我们在同一文件中指定了所有状态选项。

Then we create another beforeEach block with inject which is used to inject the AngularJS $controller service. We set _$controller_ to the $controller variable we created and then create an instance of our controller by calling $controller('UsersController', {}). The first argument is the name of the controller we want to test and the second argument is an object of the dependencies for our controller. We'll leave it empty for now since we're trying to keep this test as simple as possible.

然后,我们创建另一个beforeEach与块inject是用来注入AngularJS $controller 服务 。 我们将_$controller_设置_$controller_我们创建的$controller变量,然后通过调用$controller('UsersController', {})创建控制器的实例。 第一个参数是我们要测试的控制器的名称,第二个参数是控制器依赖关系的对象。 我们暂时将其保留为空,因为我们试图使此测试尽可能简单。

Finally, we end this file with a basic test for the existence of our controller with the expectation that it should be defined.

最后,我们以对该控制器存在的基本测试结束该文件,并期望应该对其进行定义。

The one line of code $controller = _$controller_; may seem unnecessary here when we could simply write UsersController = _$controller_('UsersController', {});. That would be completely valid in this specific case but in some of our later tests we'll need to instantiate controllers with different dependencies and that $controller variable will be needed. This will make more sense once we get to those tests.

一行代码$controller = _$controller_; 当我们可以简单地编写UsersController = _$controller_('UsersController', {});这里似乎没有必要UsersController = _$controller_('UsersController', {}); 。 在这种特定情况下,这将是完全有效的,但是在我们以后的一些测试中,我们将需要实例化具有不同依赖关系的控制器,并且将需要$controller变量。 一旦我们进行了这些测试,这将变得更加有意义。

With that test file written update your karma.conf.js file to include our new test file within the files property along with the file for our controller which we're about to define.

编写该测试文件后,更新您的karma.conf.js文件,以将我们的新测试文件包括在files属性中,以及将要定义的控制器文件。

files: ['./node_modules/angular/angular.js','./node_modules/angular-ui-router/release/angular-ui-router.js','./node_modules/angular-mocks/angular-mocks.js','./app/services/users/users.js','./app/components/users/users.js','./app/app.js','./app/services/users/users.spec.js','./app/components/users/users.spec.js'],

Restart Karma and you should now see a failing test stating our module components.users cannot be found. Let's create that and get this test to pass.

重新启动Karma,现在应该看到测试失败,说明我们的模块components.users 。找不到用户。 让我们创建它并通过此测试。

Open up components/users/users.js and add the following code.

打开components/users/users.js并添加以下代码。

(function() {'use strict';// Define the component and controller we loaded in our testangular.module('components.users', []).controller('UsersController', function() {var vm = this;}).config(function($stateProvider) {$stateProvider.state('users', {url: '/users',templateUrl: 'components/users/users.html',controller: 'UsersController as uc'});});
})();

Here we've declared our component components.users and the controller itself UsersController as we specified in our test file. In addition to this, we've also added a configuration for this file including its state, url, template, and controller. This configuration is why we included ui.router in our test file.

在这里,我们已经声明了组件components.users和控制器本身的UsersController正如我们在测试文件中指定的那样。 除此之外,我们还为该文件添加了一个配置,包括其状态,URL,模板和控制器。 此配置是为什么我们在测试文件中包含ui.router原因。

Save that file, restart Karma if it isn't already running, and you should now see a passing test for UsersController should be defined.

保存该文件,如果Karma尚未运行,请重新启动它,现在您应该看到UsersController should be definedUsersController should be defined的通过测试。

Now that we know our test is working at the most basic level we need to test the call to our service to get a list of users so we can populate our view. Open up /components/users/users.spec.js again and update it with another test.

现在我们知道我们的测试在最基本的级别上进行,我们需要测试对服务的调用以获取用户列表,以便我们填充视图。 再次打开/components/users/users.spec.js并使用另一个测试对其进行更新。

describe('UsersController', function() {var $controller, UsersController, UsersFactory;// Mock the list of users we expect to use in our controllervar userList = [{ id: '1', name: 'Jane', role: 'Designer', location: 'New York', twitter: 'gijane' },{ id: '2', name: 'Bob', role: 'Developer', location: 'New York', twitter: 'billybob' },{ id: '3', name: 'Jim', role: 'Developer', location: 'Chicago', twitter: 'jimbo' },{ id: '4', name: 'Bill', role: 'Designer', location: 'LA', twitter: 'dabill' }];beforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('components.users'));// Add the module for our Users servicebeforeEach(angular.mock.module('api.users'));beforeEach(inject(function(_$controller_, _Users_) {$controller = _$controller_;UsersFactory = _Users_;// Spy and force the return value when UsersFactory.all() is calledspyOn(UsersFactory, 'all').and.callFake(function() {return userList;});// Add the factory as a controller dependencyUsersController = $controller('UsersController', { Users: UsersFactory });}));it('should be defined', function() {expect(UsersController).toBeDefined();});// Add a new test for our expected controller behaviorit('should initialize with a call to Users.all()', function() {expect(UsersFactory.all).toHaveBeenCalled();expect(UsersController.users).toEqual(userList);});
});

Starting at the top we've added another variable UsersFactory which we'll set to our injected Users service. After that, we've added an array of users which we borrowed from the Users service from Part 1 of this tutorial which can be found in /services/users/users.js. Then, we load the module api.users using angular-mocks. In the following beforeEach block we inject our service Users using the underscore wrapping convention and set it to our local UsersFactory variable.

从顶部开始,我们添加了另一个变量UsersFactoryUsersFactory其设置为注入的Users服务。 之后,我们添加了一系列用户,这些Users从本教程第1部分的Users服务中借用的,可以在/services/users/users.js找到。 然后,我们使用angular-mocks加载模块api.users 。 在下面的beforeEach块中,我们使用下划线包装约定注入服务Users并将其设置为我们的本地UsersFactory变量。

After that we add a spy to the all method of our factory and chain it with another one of Jasmine's built-in functions callFake. The callFake method allows us to intercept a call to that method and supply it our own return value. In this case, we're returning userList which we defined at the top of this file. Finally, we add our service as a dependency to UsersController when we call $controller. The property value Users refers to the service we'll inject into our actual controller and the value UsersFactory is a reference to the service we injected just two lines above it.

之后,我们将间谍添加到工厂的all方法中,并将其与Jasmine的另一个内置函数 callFakecallFake方法允许我们拦截对该方法的调用,并为其提供我们自己的返回值。 在这种情况下,我们将返回在此文件顶部定义的userList 。 最后,当我们调用$controller时,将服务添加为对UsersController的依赖。 属性值Users是指我们将注入到实际控制器中的服务,而值UsersFactory是对我们在其上方仅两行注入的服务的引用。

It's important to remember that our tests are testing expectations and not the actual implementation of our code. In this test file, we use Jasmine's callFake function to intercept the actual call and return a hardcoded list of users (our expectation). Our tests for that service don't belong here. It was already handled in Part 1 of this tutorial and the test for that method is located within /services/users/users.spec.js.

重要的是要记住,我们的测试只是在测试期望,而不是代码的实际实现。 在此测试文件中,我们使用Jasmine的callFake函数来拦截实际的调用并返回用户的硬编码列表(我们的期望)。 我们对该服务的测试不属于此处。 本教程的第1部分已经对其进行了处理,并且该方法的测试位于/services/users/users.spec.js

Finally, we add a test spec for our controller with a new expectation: should initialize with a call to Users.all(). The test has two expectations. The first expectation uses the spy we declared above and simply expects that a call to the all method will be made. The second expectation expects that the controller's view-model property users will be set to the list of users we defined above. Save the file so Karma shows a failing test and let's add the real code to our controller which should help clarify our test.

最后,我们为控制器添加了一个新的期望的测试规范: should initialize with a call to Users.all() 。 该测试有两个期望。 第一个期望使用上面我们声明的间谍,并且简单地期望将对all方法进行调用。 第二个期望是将控制器的view-model属性users设置为我们在上面定义的用户列表。 保存文件,以便Karma显示失败的测试,让我们将真实代码添加到我们的控制器中,这将有助于阐明我们的测试。

Open up our controller file /components/users/users.js and update it to make our failing tests pass.

打开我们的控制器文件/components/users/users.js并对其进行更新,以使失败的测试通过。

(function() {'use strict';angular.module('components.users', []).controller('UsersController', function(Users) { // Add Users factoryvar vm = this;// Call all() and set it to usersvm.users = Users.all();}).config(function($stateProvider) {$stateProvider.state('users', {url: '/users',templateUrl: 'components/users/users.html',controller: 'UsersController as uc'});});
})();

We've added the Users service as a dependency to our controller and also initialize a call to Users.all and set the return value to vm.users. Save that file and our previously failing test should now pass.

我们已经将Users服务添加为控制器的依赖项,并且还初始化了对Users.all的调用, Users.all返回值设置为vm.users 。 保存该文件,我们以前失败的测试现在应该通过。

We've now created and tested the controller for our users and our users are waiting to be displayed in the browser. Open up our empty template /components/users/users.html and add the following code to iterate over our users in the UsersController.

现在,我们已经为我们的用户创建并测试了控制器,我们的用户正等待在浏览器中显示。 打开我们的空模板/components/users/users.html并添加以下代码以遍历UsersController的用户。

<div class="container"><div class="row"><div class="col-md-4" ng-repeat="user in uc.users"><div class="panel panel-default"><div class="panel-heading"><h3 class="panel-title text-center">{{user.name}}</h3></div><div class="panel-body"><div><span class="glyphicon glyphicon-briefcase" aria-hidden="true"></span> {{user.role}}</div><div><span class="glyphicon glyphicon-map-marker" aria-hidden="true"></span> {{user.location}}</div><div><span class="glyphicon glyphicon-link" aria-hidden="true"></span> {{user.twitter}}</div></div></div></div></div>
</div>

There's one last step to get this working in the browser. Until now we've only been adding our files to karma.conf.js. First, add the controller and service to index.html.

要使此功能在浏览器中工作,还有最后一步。 到目前为止,我们仅将文件添加到karma.conf.js 。 首先,将控制器和服务添加到index.html

<head>......<script src="services/users/users.js"></script><script src="components/users/users.js"></script><script src="app.js"></script>
</head>

Then add the modules to our application dependencies in app/app.js. While we're here, update $urlRouterProvider.otherwise('/') to default to the users state we just created.

然后将模块添加到app/app.js中的app/app.js程序依赖项中。 在这里时,将$urlRouterProvider.otherwise('/')更新$urlRouterProvider.otherwise('/')默认设置为我们刚刚创建的users状态。

(function() {'use strict';angular.module('meetIrl', ['ui.router','api.users','components.users']).config(function($urlRouterProvider) {$urlRouterProvider.otherwise('/users');});
})();

Run nodemon server.js, navigate to http://localhost:8080/#/users, and you should see our four users.

运行nodemon server.js ,导航到http://localhost:8080/#/users ,您应该看到我们的四个用户。

测试Angular Factory和Real API端点 ( Testing an Angular Factory and a Real API Endpoint )

In Part 1 we covered how to test an Angular factory and now we've tested our first controller which consumes that same factory. But that was a simple factory that returned a hard-coded list of users. How do we test a factory that makes an actual HTTP request to a real API? As I mentioned earlier we're going to display an avatar of each user's favorite Pokémon on their individual profile page using Pokéapi.

在第1部分中,我们介绍了如何测试Angular工厂,现在我们已经测试了使用该工厂的第一个控制器。 但这是一个简单的工厂,返回了硬编码的用户列表。 我们如何测试对真实API发出实际HTTP请求的工厂? 如前所述,我们将使用Pokéapi在每个用户的个人资料页面上显示每个用户喜欢的Pokémon的头像。

First, let's create a new directory in our services folder for our Pokemon service.

首先,让我们在我们的服务文件夹中为Pokemon服务创建一个新目录。

cd app/services
mkdir pokemon && cd pokemon
touch pokemon.js pokemon.spec.js

Within our factory we'll have one method, findByName, which makes a GET request to the /pokemon/ endpoint which you can see here. This one request will provide us everything we need to populate our user profiles with all the necessary Pokémon data.

在我们的工厂内,我们将有一个方法findByName ,它向/pokemon/端点发出GET请求,您可以在此处看到。 这项要求将为我们提供所有必要的神奇宝贝数据,以填充用户资料所需的一切。

Like we've done previously we'll first set up a basic test and a basic factory to ensure everything is working correctly. Open up /services/pokemon/pokemon.spec.js and add the following code.

像我们之前所做的一样,我们将首先设置一个基本测试和一个基本工厂,以确保一切正常。 打开/services/pokemon/pokemon.spec.js并添加以下代码。

describe('Pokemon factory', function() {var Pokemon;// Load the api.pokemon module which we'll create nextbeforeEach(angular.mock.module('api.pokemon'));// Inject the Pokemon servicebeforeEach(inject(function(_Pokemon_) {Pokemon = _Pokemon_;}));// Verify our controller existsit('should exist', function() {expect(Pokemon).toBeDefined();});
});

Then update karma.conf.js accordingly with our two new Pokémon files.

然后使用两个新的Pokémon文件相应地更新karma.conf.js

files: ['./node_modules/angular/angular.js','./node_modules/angular-ui-router/release/angular-ui-router.js','./node_modules/angular-mocks/angular-mocks.js','./app/services/users/users.js','./app/services/pokemon/pokemon.js','./app/components/users/users.js','./app/app.js','./app/services/users/users.spec.js','./app/services/pokemon/pokemon.spec.js','./app/components/users/users.spec.js'],

Restarting Karma should show a failing test. Add the following code to /services/pokemon/pokemon.js to make our test pass.

重新启动Karma应该显示测试失败。 将以下代码添加到/services/pokemon/pokemon.js以使我们的测试通过。

(function() {'use strict';// Define the component and controller we loaded in our testangular.module('api.pokemon', []).factory('Pokemon', function() {var Pokemon = {};return Pokemon;});
})();

Within our test file for our Pokemon service we are going to handle two cases, or response types, from Pokéapi. The first is a GET request with a valid Pokémon name. In this scenario, we'll use the successful response to populate our user profile image with an image of their favorite Pokémon. The second will be for a request with an invalid Pokémon name. In this case, we'll set the user profile image to a placeholder image to preserve the look of the profile page. Let's handle the valid API request first. Jump back into /services/pokemon/pokemon.spec.js and update it with the following code.

在用于Pokemon服务的测试文件中,我们将处理Pokéapi的两种情况或响应类型。 第一个是带有有效神奇宝贝名称的GET请求。 在这种情况下,我们将使用成功的响应,用他们最喜欢的神奇宝贝的图像填充我们的用户个人资料图像。 第二个将是带有无效神奇宝贝名称的请求。 在这种情况下,我们会将用户个人资料图像设置为占位符图像,以保留个人资料页面的外观。 让我们先处理有效的API请求。 跳回到/services/pokemon/pokemon.spec.js并使用以下代码对其进行更新。

describe('Pokemon factory', function() {var Pokemon, $q, $httpBackend;// Add Pokeapi endpointvar API = 'http://pokeapi.co/api/v2/pokemon/';// Add mocked Pokéapi responsevar RESPONSE_SUCCESS = {'id': 25,'name': 'pikachu','sprites': {'front_default': 'http://pokeapi.co/media/sprites/pokemon/25.png'},'types': [{'type': { 'name': 'electric' }}]};beforeEach(angular.mock.module('api.pokemon'));// Inject $q and $httpBackend for testing HTTP requestsbeforeEach(inject(function(_Pokemon_, _$q_, _$httpBackend_) {Pokemon = _Pokemon_;$q = _$q_;$httpBackend = _$httpBackend_;}));it('should exist', function() {expect(Pokemon).toBeDefined();});describe('findByName()', function() {var result;beforeEach(function() {// Initialize our local result object to an empty object before each testresult = {};// Spy on our service call but allow it to continue to its implementationspyOn(Pokemon, "findByName").and.callThrough();});it('should return a Pokemon when called with a valid name', function() {var search = 'pikachu';// Declare the endpoint we expect our service to hit and provide it with our mocked return values$httpBackend.whenGET(API + search).respond(200, $q.when(RESPONSE_SUCCESS));expect(Pokemon.findByName).not.toHaveBeenCalled();expect(result).toEqual({});Pokemon.findByName(search).then(function(res) {result = res;});// Flush pending HTTP requests$httpBackend.flush();expect(Pokemon.findByName).toHaveBeenCalledWith(search);expect(result.id).toEqual(25);expect(result.name).toEqual('pikachu');expect(result.sprites.front_default).toContain('.png');expect(result.types[0].type.name).toEqual('electric');});})
});

At the top of this file we've added a few more variables: $httpBackend, $q, API, and RESPONSE_SUCCESS. API simply serves as a variable for the Pokéapi endpoint we're hitting and RESPONSE_SUCCESS is one example of a successful response from Pokéapi for the resource "pikachu". If you look at the example response in the documentation or hit the endpoint yourself with Postman you'll see there is a lot of data that's returned. We'll only be using a small set of that data so we've removed everything else while maintaining the data structure of the response for these four fields.

在此文件的顶部,我们添加了更多变量: $httpBackend$qAPIRESPONSE_SUCCESSAPI只是用作我们要访问的Pokéapi端点的变量,而RESPONSE_SUCCESS是Pokéapi对资源“ pikachu”成功响应的一个示例。 如果您查看文档中的示例响应,或者自己用Postman命中端点,您将看到返回了很多数据。 我们将只使用一小部分数据,因此在保留这四个字段的响应数据结构的同时,我们删除了所有其他内容。

We then set $q and $httpBackend to their respective injected services in our second beforeEach call. The $q service allows us to simulate resolving or rejecting a promise which is important when testing asynchronous calls. The $httpBackend service allows us to verify whether or not our Pokemon factory makes an HTTP request to Pokéapi without actually hitting the endpoint itself. The two of these services combined provide us the ability to verify a request was made to the API while also giving us the option to resolve or reject the response depending on which response we are testing.

然后,在第二个beforeEach调用中,将$q$httpBackend为它们各自的注入服务。 $q服务允许我们模拟解析或拒绝诺言,这在测试异步调用时很重要。 $httpBackend服务使我们能够验证Pokemon工厂是否向Pokéapi发出HTTP请求,而无需实际访问端点本身。 这两项服务的结合使我们能够验证对API的请求,同时还可以根据我们测试的响应来选择解析还是拒绝响应。

Remember that an API and it's various responses are expectations of our application. We're merely testing that our application will be able to consume those various responses. As mentioned earlier, we'll want to set the profile image to a Pokémon if it's valid or default to a placeholder image if the request is invalid.

请记住,API及其各种响应是我们应用程序的期望 。 我们只是在测试我们的应用程序将能够使用那些各种响应。 如前所述,我们希望将个人资料图片设置为“神奇宝贝”(如果有效),或者将默认设置为占位符图片(如果请求无效)。

Below our previous test we've added another describe block for the findByName method which will make an HTTP request to the Pokeapi. We declare a variable result which will be set to the result of our service call and set it to an empty object before each test is run in our beforeEach block. We also create a spy on the findByName method and chain it with another one of Jasmine's built-in functions callThrough. By chaining the spy with callThrough we have the ability to track any calls made to this function but the implementation will continue to the HTTP request that will be made within the function itself.

在之前的测试下面,我们为findByName方法添加了另一个describe块,该块将向Pokeapi发出HTTP请求。 我们声明一个变量result ,该结果将设置为服务调用的结果,并在每次测试在beforeEach块中运行之前将其设置为空对象。 我们还将在findByName方法上创建一个间谍并将其与Jasmine的另一个内置函数 callThrough 。 通过将间谍与callThrough链接callThrough我们可以跟踪对此函数的所有调用,但是实现将继续对将在函数本身中进行的HTTP请求进行跟踪。

Finally, we have our test spec for an API call to Pokéapi service with a valid Pokemon name. After declaring our search value as "pikachu" we make our first use of the $httpBackend service we injected earlier. Here we've called the whenGET method and supplied it with the API variable we defined earlier along with our search term "pikachu". We then chain it with respond and provide it two arguments: 200 as the status code and RESPONSE_SUCCESS as its return value wrapped with $q.when. When $q.when wraps a value it converts it into a simulated resolved "then-able" promise which is the behavior we'd expect when calling an Angular service that returns a promise. So in plain English this says, "When a GET request is made to http://pokeapi.co/api/v2/pokemon/pikachu, respond with a 200 status code and the resolved response object we created earlier."

最后,我们具有使用有效的Pokemon名称对API调用Pokéapi服务的测试规范。 在将search值声明为“ pikachu”之后,我们首先使用了先前注入的$httpBackend服务。 在这里,我们调用了whenGET方法,并为其提供了我们之前定义的API变量以及搜索词“ pikachu”。 然后,我们用IT连锁respond ,并为其提供两个参数:200状态码和RESPONSE_SUCCESS作为包裹着它的返回值$q.when 。 当$q.when包装一个值时,它会将其转换为模拟的已解析“ then-able” promise,这是我们在调用返回$q.when的Angular服务时期望的行为。 因此,用通俗的英语说:“当对http://pokeapi.co/api/v2/pokemon/pikachu进行GET请求时,以200状态代码和我们之前创建的已解析响应对象进行响应。”

After this we create two expectations: one for the initial state of our result variable and another for the Pokemon service call. We're expecting our spy on findByName not to have been called and the result variable to be an empty object. Then we call Pokemon.findByName, pass in our search term and chain it with .then where we set the returned result to our local result variable. After that we call $httpBackend.flush.

此后,我们创建两个期望:一个期望结果变量的初始状态,另一个期望用于Pokemon服务调用。 我们期望未调用findByName上的间谍,并且结果变量为空对象。 然后我们调用Pokemon.findByName ,通过我们的搜索项和IT连锁与.then ,我们设定的返回结果给我们当地的result变量。 之后,我们调用$httpBackend.flush

If we were to call Pokemon.findByName in a controller, the service's $http request would respond asynchronously. Within our unit tests this asynchronous behavior would be difficult to test. Thankfully, Angular's $httpBackend service provides us the ability to "flush" pending requests so we can write our tests in a synchronous manner. Because of this, it is important that any expectations we have that would come after an asynchronous call is finished are placed after our $httpBackend.flush() call in our test.

如果我们要在控制器中调用Pokemon.findByName ,则该服务的$http请求将异步响应。 在我们的单元测试中,这种异步行为将很难测试。 幸运的是,Angular的$httpBackend服务使我们能够“刷新”未决请求,因此我们可以以同步方式编写测试。 正因为如此,重要的是我们有什么期待要跟从异步调用完成是我们的后放置很重要, $httpBackend.flush()在我们的测试呼叫。

Finally, we create our final set of expectations from the result of our service call. Our first expectation utilizes the spy we created earlier to verify our service was called with the correct search term and the remaining four expectations verify that our result object contains all of the data related to Pikachu. Save that file and you should now see Karma showing a failing test.

最后,我们根据服务电话的结果创建最终的期望值。 我们的第一个期望利用我们之前创建的间谍来验证是否使用正确的搜索词调用了我们的服务,其余四个期望则证明了我们的结果对象包含与皮卡丘有关的所有数据。 保存该文件,您现在应该看到Karma显示测试失败。

We can get this test to pass in our service with just a few small additions. Open up /services/pokemon/pokemon.js and add the findByName method.

我们可以通过少量添加使该测试通过我们的服务。 打开/services/pokemon/pokemon.js并添加findByName方法。

(function() {'use strict';angular.module('api.pokemon', []).factory('Pokemon', function($http) {  // Add $http dependencyvar API = 'http://pokeapi.co/api/v2/pokemon/';var Pokemon = {};// Spy on this method chained with callThrough() allows it to continue to continue to $http.get()Pokemon.findByName = function(name) {return $http.get(API + name).then(function(res) {return res.data;});};return Pokemon;});
})();

Before adding the findByName method itself, we've injected the $http service into our factory and also created an API variable set to the Pokéapi endpoint we want to hit similar to the way we did in our test. After that we declare the findByName method and make a GET request to the endpoint with the name provided to us when the service is called. When the promise is fulfilled, we return the response's data property. Save that change and your first test for an Angular factory hitting a real API should now be passing!

在添加findByName方法本身之前,我们已经将$http服务注入了我们的工厂,并且还创建了一个API变量集,该变量集设置为我们想要命中的Pokéapi端点,这与测试中的方法类似。 之后,我们声明findByName方法,并使用调用服务时提供给我们的名称向端点发出GET请求。 兑现承诺后,我们将返回响应的data属性。 保存该更改,您的Angular工厂首次使用真实API的测试现在应该通过了!

That test handles our first case where a request is made to Pokéapi with a valid Pokemon. But we still need to handle the case where we make a request to the API with an invalid Pokémon. Within the context of our factory, we're going to test that we're able to catch a promise rejection from the API. Go back into /services/pokemon/pokemon.spec.js and add another test spec for our findByName method.

该测试处理了我们的第一种情况,即使用有效的Pokemon向Pokéapi发出请求。 但是,我们仍然需要处理使用无效的神奇宝贝向API发出请求的情况。 在工厂的上下文中,我们将测试是否能够从API catch承诺。 返回到/services/pokemon/pokemon.spec.js并为我们的findByName方法添加另一个测试规范。

describe('Pokemon factory', function() {var Pokemon, $q, $httpBackend;var API = 'http://pokeapi.co/api/v2/pokemon/';var RESPONSE_SUCCESS = {'id': 25,'name': 'pikachu','sprites': {'front_default': 'http://pokeapi.co/media/sprites/pokemon/25.png'},'types': [{'type': { 'name': 'electric' }}]};// Add new mocked Pokéapi responsevar RESPONSE_ERROR = {'detail': 'Not found.'};beforeEach(angular.mock.module('api.pokemon'));beforeEach(inject(function(_Pokemon_, _$q_, _$httpBackend_) {Pokemon = _Pokemon_;$q = _$q_;$httpBackend = _$httpBackend_;}));it('should exist', function() {expect(Pokemon).toBeDefined();});describe('findByName()', function() {var result;beforeEach(function() {result = {};spyOn(Pokemon, "findByName").and.callThrough();});it('should return a Pokemon when called with a valid name', function() {var search = 'pikachu';$httpBackend.whenGET(API + search).respond(200, $q.when(RESPONSE_SUCCESS));expect(Pokemon.findByName).not.toHaveBeenCalled();expect(result).toEqual({});Pokemon.findByName(search).then(function(res) {result = res;});$httpBackend.flush();expect(Pokemon.findByName).toHaveBeenCalledWith(search);expect(result.id).toEqual(25);expect(result.name).toEqual('pikachu');expect(result.sprites.front_default).toContain('.png');expect(result.types[0].type.name).toEqual('electric');});it('should return a 404 when called with an invalid name', function() {// Update search termvar search = 'godzilla';// Update status code and response object (reject instead of when/resolve)$httpBackend.whenGET(API + search).respond(404, $q.reject(RESPONSE_ERROR));expect(Pokemon.findByName).not.toHaveBeenCalled();expect(result).toEqual({});// Update chained method to catchPokemon.findByName(search).catch(function(res) {result = res;});$httpBackend.flush();expect(Pokemon.findByName).toHaveBeenCalledWith(search);expect(result.detail).toEqual('Not found.');});});
});

This new test is nearly identical to our previous test. At the top of our file we added another variable RESPONSE_ERROR which is the response we expect to receive if we pass it an invalid name. In our second test, we declare that we expect to receive a 404 when hitting the API with an invalid name. From there we change our search term from "pikachu" to "godzilla" and update our whenGET to respond with a 404 status code and our new RESPONSE_ERROR variable wrapped with q.reject so that we can catch our rejected promise when we call Pokemon.findByName. Finally, we update our expectations for our result to test for the detail property of our response.

此新测试与我们之前的测试几乎相同。 在文件的顶部,我们添加了另一个变量RESPONSE_ERROR ,这是如果我们传递一个无效名称则希望收到的响应。 在第二个测试中,我们声明希望以无效名称访问API时会收到404。 在这里,我们将搜索词从“ pikachu”更改为“ whenGET ”,并更新whenGET以响应404状态代码和包装有q.rejectRESPONSE_ERROR变量,以便我们在调用Pokemon.findByName时可以catch被拒绝的承诺。 最后,我们更新对result的期望,以测试响应的detail属性。

The Pokéapi documentation isn't explicit about this response error object but I hit the API with multiple, incorrect search terms and received the same response every time. I also looked into the project on Github and the else statement for this call raises a 404 if it can't find a match for our given search term. The 404 is more important here anyway since we'll be defaulting to a placeholder image instead of using the returned response text in our view.

Pokéapi文档未明确说明此响应错误对象,但我使用多个错误的搜索词访问了API,每次都收到相同的响应。 我还查看了Github上的项目,如果找不到与给定搜索词匹配的项目,则此调用的else语句会引发404。 无论如何,404在这里更重要,因为我们将默认使用占位符图像,而不是在视图中使用返回的响应文本。

Save that file and Karma should now show our new test as a failing test. Go back into /services/pokemon/pokemon.js and add a catch to our HTTP request.

保存该文件,Karma现在应将我们的新测试显示为失败测试。 返回到/services/pokemon/pokemon.js并将catch添加到我们的HTTP请求中。

(function() {'use strict';angular.module('api.pokemon', []).factory('Pokemon', function($http) {var API = 'http://pokeapi.co/api/v2/pokemon/';var Pokemon = {};Pokemon.findByName = function(name) {return $http.get(API + name).then(function(res) {return res.data;}).catch(function(res) {return res.data;});};return Pokemon;});
})();

Save that file and our failing test should now be passing. We have now created an Angular factory that hits a real API and have the associated tests for both a valid and invalid response from Pokéapi. This fully tested service gives us the confidence to move on to the next and final part of our application where we create a new component for our user profile which will make the actual request to Pokéapi using our Pokemon factory.

保存该文件,我们的失败测试现在应该通过了。 现在,我们已经创建了一个使用真实API的Angular工厂,并具有来自Pokéapi的有效和无效响应的关联测试。 这项经过全面测试的服务使我们有信心继续前进到应用程序的下一部分,这是我们为用户个人资料创建一个新组件的部分,它将使用我们的Pokemon工厂向Pokéapi发出实际请求。

对我们用户的快速更新 ( A Quick Update to Our Users )

Before we get started creating the profile page for our users, we'll need to update the users in our Users service so they each have a favorite Pokémon we can use to call our Pokemon service. What can I say? I didn't expect Pokémon to make a comeback when I was writing Part 1 of this tutorial.

在开始为我们的用户创建个人资料页面之前,我们需要更新Users服务中的Users以便他们每个人都有一个喜欢的Pokémon,我们可以使用它来调用我们的Pokemon服务。 我能说什么 在编写本教程的第1部分时,我没想到神奇宝贝会卷土重来。

Open services/users/users.js and update each user in the userList with a pokemon object and a name property within that object.

打开services/users/users.js并使用pokemon对象和该对象内的name属性更新userList每个用户。

(function() {'use strict';angular.module('api.users', []).factory('Users', function() {var Users = {};var userList = [{id: '1',name: 'Jane',role: 'Designer',location: 'New York',twitter: 'gijane',pokemon: { name: 'blastoise' }},{id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }},{id: '3',name: 'Jim',role: 'Developer',location: 'Chicago',twitter: 'jimbo',pokemon: { name: 'hitmonchan' }},{id: '4',name: 'Bill',role: 'Designer',location: 'LA',twitter: 'dabill',pokemon: { name: 'barney' }}];...});
})();

You're free to use any Pokémon here you'd like but remember to update your test files accordingly once you're done. You should make updates to /services/users/users.spec.js and /components/users/users.spec.js to reflect our new list of users and their favorite Pokémon. I've also added one invalid Pokémon for the sake of having at least one user default to our placeholder image we'll add shortly.

您可以在此处随意使用任何神奇宝贝,但请记住,一旦完成,请相应地更新测试文件。 您应该对/services/users/users.spec.js/components/users/users.spec.js进行更新,以反映我们的新用户列表及其最喜欢的神奇宝贝。 我还添加了一个无效的Pokémon,以使我们至少要添加至少一个用户默认的占位符图像。

为用户配置文件创建和测试控制器 ( Creating and Testing a Controller for User Profiles )

Before we create our user profile controller and its associated test, let's take a minute to recap the expected behavior of our profile page so we can incrementally build our way to the finished feature one test case at a time. When a user navigates to a profile page for a given user, we're going to provide the user object to our controller using the resolve property provided to us by ui-router.

在创建用户配置文件控制器及其关联的测试之前,让我们花一点时间回顾一下配置文件页面的预期行为,以便我们一次一次地逐步建立最终功能的方式。 当用户导航到给定用户的配置文件页面时,我们将使用ui-router 提供给我们的resolve属性将用户对象提供给控制器。

Our first test (not including our very basic toBeDefined test) will test that our controller is instantiated with a valid resolved user. Our second test will test that a valid resolved user with a valid Pokémon will make a request to the Pokéapi using our Pokemon service. This value will eventually be set to a view-model object to be used within our view. Our third test will test that a valid resolved user with an invalid Pokémon will make a request to the Pokéapi using our Pokemon service, catch the rejection, and default our view-model object to a placeholder image. Finally, we'll add one extra test for a user that doesn't exist which will redirect to a 404 page without ever making a request to the Pokéapi. Let's get started.

我们的第一个测试(不包括我们最基本的toBeDefined测试)将测试我们的控制器已被有效的解析用户实例化。 我们的第二项测试将测试具有有效神奇宝贝的有效解析用户将使用我们的Pokemon服务向神奇宝贝发出请求。 该值最终将设置为要在我们的视图中使用的视图模型对象。 我们的第三个测试将测试有效的已解析用户和无效的Pokémon,将使用我们的Pokemon服务向Pokéapi发送请求,捕获拒绝,并将视图模型对象默认为占位符图像。 最后,我们将为不存在的用户添加一个额外的测试,该测试将重定向到404页面,而无需向Pokéapi发出请求。 让我们开始吧。

We'll start by creating the directory for our user profile controller, view, and test file as usual.

我们将像往常一样为用户配置文件控制器创建目录,查看和测试文件。

cd app/components
mkdir profile && cd profile
touch profile.js profile.spec.js profile.html

Open /components/profile/profile.spec.js and we can add our basic test for the existence of our controller.

打开/components/profile/profile.spec.js ,我们可以为控制器的存在添加基本测试。

describe('components.profile', function() {var $controller;// Load ui.router and our components.profile module which we'll create nextbeforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('components.profile'));// Inject the $controller servicebeforeEach(inject(function(_$controller_) {$controller = _$controller_;}));describe('ProfileController', function() {var ProfileController;beforeEach(function() {// Create an instance of our controllerProfileController = $controller('ProfileController', { });});// Verify our controller existsit('should be defined', function() {expect(ProfileController).toBeDefined();});});
});

Then add our two new files to karma.conf.js.

然后将我们的两个新文件添加到karma.conf.js

files: ['./node_modules/angular/angular.js','./node_modules/angular-ui-router/release/angular-ui-router.js','./node_modules/angular-mocks/angular-mocks.js','./app/services/users/users.js','./app/services/pokemon/pokemon.js','./app/components/users/users.js','./app/components/profile/profile.js','./app/app.js','./app/services/users/users.spec.js','./app/services/pokemon/pokemon.spec.js','./app/components/users/users.spec.js','./app/components/profile/profile.spec.js'],

Restarting Karma should show a failing test. Add the following to /components/profile/profile.js to make it pass.

重新启动Karma应该显示测试失败。 将以下内容添加到/components/profile/profile.js以使其通过。

(function() {'use strict';// Define the component and controller we loaded in our testangular.module('components.profile', []).controller('ProfileController', function() {var vm = this;}).config(function($stateProvider) {$stateProvider.state('profile', {url: '/user/:id',templateUrl: 'components/profile/profile.html',controller: 'ProfileController as pc'});});
})();

Now we're ready for our first real test to verify that our controller is instantiated with a resolved user object. Go back into /components/profile/profile.spec.js and add our new test.

现在,我们准备进行第一个实际测试,以验证是否已使用解析的用户对象实例化了控制器。 返回/components/profile/profile.spec.js并添加我们的新测试。

describe('components.profile', function() {var $controller;beforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('components.profile'));beforeEach(inject(function(_$controller_) {$controller = _$controller_;}));describe('ProfileController', function() {var ProfileController, singleUser;beforeEach(function() {// Define singleUser and add resolvedUser as a dependency to our controller singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};ProfileController = $controller('ProfileController', { resolvedUser: singleUser });});it('should be defined', function() {expect(ProfileController).toBeDefined();});});describe('Profile Controller with a valid resolved user', function() {var ProfileController, singleUser;beforeEach(function() {// Mock a valid usersingleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};// Add the valid user as our resolved dependencyProfileController = $controller('ProfileController', { resolvedUser: singleUser });});it('should set the view model user object to the resolvedUser', function() {expect(ProfileController.user).toEqual(singleUser);});});
});

Here we've added a new describe block for our tests related to a valid resolved user. This test is similar to our previous test except we've added another beforeEach call to mock our single resolved user. We then pass it in as a dependency to our controller and add an expectation that the resolved user will be set to the user view-model property in our controller.

在这里,我们为与有效的已解析用户相关的测试添加了一个新的describe块。 该测试与之前的测试类似,不同之处在于,我们添加了另一个beforeEach调用来模拟单个已解析的用户。 然后,将其作为依赖项传递给我们的控制器,并期望将解析的用户设置为控制器中的user view-model属性。

Since our tests serve as a form of documentation for our actual code, I've also added a singleUser to our previous test and passed it in as a dependency to that controller instance as well. We didn't specify any expectations about the resolved user within that test but it keeps our controller declarations consistent with our actual controller and the other controller declarations within this file. As we continue to add more dependencies in this test file, we'll go back and update our other tests to reflect this.

由于我们的测试是我们实际代码的一种文档形式,因此我还向先前的测试中添加了singleUser ,并将其作为对该控制器实例的依赖关系传入。 在该测试中,我们没有对解析的用户指定任何期望,但是它使我们的控制器声明与实际控制器以及该文件中的其他控制器声明保持一致。 当我们继续在此测试文件中添加更多依赖项时,我们将返回并更新其他测试以反映这一点。

To get this test to pass, go back into /components/profile/profile.js and update it with our new resolved property.

要使此测试通过,请返回/components/profile/profile.js并使用我们新的已解析属性对其进行更新。

(function() {'use strict';angular.module('components.profile', []).controller('ProfileController', function(resolvedUser) {var vm = this;vm.user = resolvedUser;}).config(function($stateProvider) {$stateProvider.state('profile', {url: '/user/:id',templateUrl: 'components/profile/profile.html',controller: 'ProfileController as pc',resolve: {// Add resolvedUser with a call to Users using $stateParamsresolvedUser: function(Users, $stateParams) {return Users.findById($stateParams.id);}}});});
})();

Here we add the new resolve property to our controller configuration with resolvedUser being set to the user returned by our Users.findById method. Within the controller, we then set this to our view-model user property as we stated within our test. Once again, we're not concerned with testing expectations related to our Users service here. That's delegated to the test file for our service in /services/users/users.spec.js.

在这里,我们新添加resolve属性,以我们的控制器配置resolvedUser设定为我们返回的用户Users.findById方法。 然后,在控制器中,如我们在测试中所述,将其设置为视图模型user属性。 再一次,我们不关心与此处的“ Users服务相关的测试期望。 这被委托给/services/users/users.spec.js我们服务的测试文件。

Now that we've finished our first test for a valid resolved user, let's move on to testing a call to the Pokemon service using the resolved user's Pokemon. Go back into /components/profile/profile.spec.js and add the following updates.

现在,我们已经完成了对有效的已解析用户的首次测试,让我们继续使用已解析用户的Pokemon测试对Pokemon服务的呼叫。 返回到/components/profile/profile.spec.js并添加以下更新。

describe('components.profile', function() {var $controller, PokemonFactory, $q, $httpBackend;var API = 'http://pokeapi.co/api/v2/pokemon/';var RESPONSE_SUCCESS = {'id': 58,'name': 'growlithe','sprites': {'front_default': 'http://pokeapi.co/media/sprites/pokemon/58.png'},'types': [{'type': { 'name': 'fire' }}]};// Load Pokemon servicebeforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('api.pokemon'));beforeEach(angular.mock.module('components.profile'));// Inject Pokemon factory, $q, and $httpBackend for testing HTTP requestsbeforeEach(inject(function(_$controller_, _Pokemon_, _$q_, _$httpBackend_) {$controller = _$controller_;PokemonFactory = _Pokemon_;$q = _$q_;$httpBackend = _$httpBackend_;}));describe('ProfileController', function() {var ProfileController, singleUser;beforeEach(function() {singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};// Add Pokemon dependencyProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory });});it('should be defined', function() {expect(ProfileController).toBeDefined();});});// Update title to include a valid Pokémondescribe('Profile Controller with a valid resolved user and a valid Pokémon', function() {var singleUser, ProfileController;beforeEach(function() {singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};// Add spy to service callspyOn(PokemonFactory, "findByName").and.callThrough();// Add PokemonFactory as a controller dependencyProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory });});it('should set the view model user object to the resolvedUser', function() {expect(ProfileController.user).toEqual(singleUser);});it('should call Pokemon.findByName and return a Pokemon object', function() {// Add expectations before the request is finishedexpect(ProfileController.user.pokemon.id).toBeUndefined();expect(ProfileController.user.pokemon.name).toEqual('growlithe');expect(ProfileController.user.pokemon.image).toBeUndefined();expect(ProfileController.user.pokemon.type).toBeUndefined();// Add our HTTP request expectation and resolved response value$httpBackend.whenGET(API + singleUser.pokemon.name).respond(200, $q.when(RESPONSE_SUCCESS));$httpBackend.flush();// Add expectations after the request is finishedexpect(PokemonFactory.findByName).toHaveBeenCalledWith('growlithe');expect(ProfileController.user.pokemon.id).toEqual(58);expect(ProfileController.user.pokemon.name).toEqual('growlithe');expect(ProfileController.user.pokemon.image).toContain('.png');expect(ProfileController.user.pokemon.type).toEqual('fire');});});
});

Starting at the top we've added a few more variables. PokemonFactory, $q, and $httpBackend will be set to their respective injected services. API and RESPONSE_SUCCESS will be used when we test our controller's call to Pokéapi using our Pokemon service. After that we load our api.pokemon module and set all of our variables to their injected services.

从顶部开始,我们添加了更多变量。 PokemonFactory$q$httpBackend将被设置为它们各自的注入服务。 当我们使用Pokemon服务测试控制器对Pokéapi的调用时,将使用APIRESPONSE_SUCCESS 。 之后,我们加载api.pokemon模块并将所有变量设置为其注入的服务。

Then, we updated our second describe title to include our expectation that this test will be working with a valid resolved user with a valid Pokémon. In the beforeEach within this describe we add a spy to our PokemonFactory's findByName method and chain it with callThrough so that our call to the service continues on to the actual HTTP request within the service. We also add the PokemonFactory as a dependency to both of our controller instances.

然后,我们更新了第二个describe标题,以期望该测试将与具有有效神奇宝贝的有效解析用户一起使用。 在此describebeforeEach describe我们将间谍程序添加到PokemonFactoryfindByName方法中,并将其与callThrough以便对服务的调用继续对服务中的实际HTTP请求进行。 我们还将PokemonFactory添加为两个控制器实例的依赖项。

Below our previous test we then add another expectation for our controller that it will make a request using our Pokemon service. Similar to our service test, we use $httpBackend's whenGET to state the API endpoint we expect to hit and supply it with a 200 status code and our RESPONSE_SUCCESS variable we defined at the top of this file. We then flush our asynchronous request to Pokéapi and list all of our expectations for the result and the view-model properties they will be set to.

在我们之前的测试之下,然后我们为控制器添加了另一个期望,即它将使用我们的Pokemon服务发出请求。 与我们的服务测试类似,我们使用$httpBackendwhenGET声明我们希望命中的API端点,并为其提供200状态代码以及我们在此文件顶部定义的RESPONSE_SUCCESS变量。 然后,我们flush对Pokéapi的异步请求,并列出对结果的所有期望以及将它们设置为的视图模型属性。

Unlike our service test we don't actually call Pokemon.findByName here directly. Instead, that call will occur within our controller after we set our view-model's users attribute to the resolved user object as we did earlier. The expectations before that call occurs within our controller are placed above $httpBackend.flush and the expectations after that asynchronous call finishes are placed after $httpBackend.flush. This goes back to Angular's $httpBackend service providing us the ability to test asynchronous calls in a synchronous manner within our tests. As far as $httpBackend.whenGET is concerned, that can be placed anywhere within this it block and even above within our beforeEach block for this test suite. That line simply waits for a request to be made to the endpoint and responds accordingly. flush() is the magic line which triggers our service call to resolve or reject within our test case.

与我们的服务测试不同,我们实际上Pokemon.findByName在此处直接调用Pokemon.findByName 。 取而代之的是,在像前面一样将视图模型的users属性设置为已解析的用户对象之后,该调用将在控制器内发生。 之前我们的控制器内发生的呼叫的期望都放在上面$httpBackend.flush和异步调用完成后的预期之后放置$httpBackend.flush 。 这可以回溯到Angular的$httpBackend服务,该服务使我们能够在测试中以同步方式测试异步调用。 就$httpBackend.whenGET而言,它可以放置在此it块内的任何位置,甚至可以放置在此测试套件的beforeEach块内的任何位置。 该行只是等待对端点的请求并做出相应的响应。 flush()是触发我们的服务调用以在我们的测试用例中解决或拒绝的魔术线。

This may be a little confusing so let's add the code to make the test pass in our controller. Go back into /components/profile/profile.js and add our call to the Pokémon service.

这可能有点令人困惑,所以让我们添加代码以使测试通过我们的控制器。 返回/components/profile/profile.js并将我们的调用添加到Pokémon服务。

(function() {'use strict';angular.module('components.profile', []).controller('ProfileController', function(resolvedUser, Pokemon) { // Add Pokemon dependencyvar vm = this;vm.user = resolvedUser;// Call our Pokemon service using our resolved user's PokemonPokemon.findByName(vm.user.pokemon.name).then(function(result) {vm.user.pokemon.id = result.id;vm.user.pokemon.image = result.sprites.front_default;vm.user.pokemon.type = result.types[0].type.name;});}).config(function($stateProvider) {$stateProvider.state('profile', {url: '/user/:id',templateUrl: 'components/profile/profile.html',controller: 'ProfileController as pc',resolve: {resolvedUser: function(Users, $stateParams) {return Users.findById($stateParams.id);}}});});
})();

First, we add the Pokemon service as a dependency to our controller. Then we call the findByName method with the resolved user's Pokémon, vm.user.pokemon.name. We then chain it with then and set all of the properties we stated earlier in our test to their respective properties in our returned result object. Before that call is made the values for id, image, and type would be undefined as we stated in our test above our call to $httpBackend.flush.

首先,我们将Pokemon服务添加为对控制器的依赖。 然后,使用解析的用户的vm.user.pokemon.name调用findByName方法。 然后,我们再将其链接起来, then将我们在测试中先前所述的所有属性设置为返回result对象中的相应属性。 在进行该调用之前,如我们在$httpBackend.flush调用上方的测试中所述, idimagetype的值将是undefined

Now that we've tested a call to the Pokemon service with a valid Pokémon, let's add the test for an invalid Pokémon. The good news is that these tests are very similar with only a few small changes.

现在,我们已经使用有效的神奇宝贝测试了对Pokemon服务的调用,现在让我们为无效的神奇宝贝添加测试。 好消息是,这些测试非常相似,只是有一些小的变化。

describe('components.profile', function() {var $controller, PokemonFactory, $q, $httpBackend;var API = 'http://pokeapi.co/api/v2/pokemon/';var RESPONSE_SUCCESS = {'id': 58,'name': 'growlithe','sprites': {'front_default': 'http://pokeapi.co/media/sprites/pokemon/58.png'},'types': [{'type': { 'name': 'fire' }}]};// Add mocked Pokéapi responsevar RESPONSE_ERROR = {'detail': 'Not found.'};beforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('api.pokemon'));beforeEach(angular.mock.module('components.profile'));beforeEach(inject(function(_$controller_, _Pokemon_, _$q_, _$httpBackend_) {$controller = _$controller_;PokemonFactory = _Pokemon_;$q = _$q_;$httpBackend = _$httpBackend_;}));describe('ProfileController', function() {var ProfileController, singleUser;beforeEach(function() {singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};ProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory });});it('should be defined', function() {expect(ProfileController).toBeDefined();});});describe('Profile Controller with a valid resolved user and a valid Pokemon', function() {var singleUser, ProfileController;beforeEach(function() {singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};spyOn(PokemonFactory, "findByName").and.callThrough();ProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory });});it('should set the view model user object to the resolvedUser', function() {expect(ProfileController.user).toEqual(singleUser);});it('should call Pokemon.findByName and return a Pokemon object', function() {expect(ProfileController.user.pokemon.id).toBeUndefined();expect(ProfileController.user.pokemon.name).toEqual('growlithe');expect(ProfileController.user.pokemon.image).toBeUndefined();expect(ProfileController.user.pokemon.type).toBeUndefined();$httpBackend.whenGET(API + singleUser.pokemon.name).respond(200, $q.when(RESPONSE_SUCCESS));$httpBackend.flush();expect(PokemonFactory.findByName).toHaveBeenCalledWith('growlithe');expect(ProfileController.user.pokemon.id).toEqual(58);expect(ProfileController.user.pokemon.name).toEqual('growlithe');expect(ProfileController.user.pokemon.image).toContain('.png');expect(ProfileController.user.pokemon.type).toEqual('fire');});});// Add our new testdescribe('Profile Controller with a valid resolved user and an invalid Pokemon', function () {var singleUser, ProfileController;beforeEach(function() {// Update Pokémon namesingleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'godzilla' }};spyOn(PokemonFactory, "findByName").and.callThrough();ProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory });});it('should call Pokemon.findByName and default to a placeholder image', function() {expect(ProfileController.user.pokemon.image).toBeUndefined();// Declare the endpoint we expect our service to hit and provide it with our mocked return values$httpBackend.whenGET(API + singleUser.pokemon.name).respond(404, $q.reject(RESPONSE_ERROR));$httpBackend.flush();// Add expectation that our image will be set to a placeholder imageexpect(PokemonFactory.findByName).toHaveBeenCalledWith('godzilla');expect(ProfileController.user.pokemon.image).toEqual('http://i.imgur.com/HddtBOT.png');});});
});

First, we add another describe block for a valid resolved user with an invalid Pokémon. Then we change our pokemon value from growlithe to godzilla. From there we change our whenGET to respond with a 404 and to reject our RESPONSE_ERROR object so that we can catch it in our controller. Finally, we update our expectations for the image property. Before the promise is rejected we expect the property to be undefined. Once the promise is actually rejected, we'll set the image property to our placeholder image.

首先,我们为带有无效神奇宝贝的有效解析用户添加另一个describe块。 然后,我们从改变我们的口袋妖怪值growlithegodzilla 。 从那里,我们更改whenGET以404响应并拒绝RESPONSE_ERROR对象,以便我们可以在控制器中catch它。 最后,我们更新了对image属性的期望。 在承诺被拒绝之前,我们希望该属性未定义。 一旦承诺被实际拒绝,我们将image属性设置为占位符图像。

Earlier in the tutorial I mentioned a seemingly unnecessary line of code: $controller = _$controller_;. This is where that's paying off. When we only had one userList to test in our UsersController we could have avoided that variable declaration. But as we see here, we're now testing a controller with a slightly modified resolvedUser. While the dependency is the same the object itself is different. In this case it's the Pokémon's name so the ability to have separate controller instances within each test block is needed to successfully test our controller.

在本教程的前面,我提到了看似不必要的代码行: $controller = _$controller_; 。 这就是回报所在。 当我们在UsersController只测试一个userList时,我们可以避免该变量声明。 但是正如我们在这里看到的,我们现在正在测试带有稍微修改的resolvedUser的控制器。 尽管依赖性相同,但对象本身却不同。 在这种情况下,这就是神奇宝贝的名字,因此需要能够在每个测试块中具有单独的控制器实例才能成功测试我们的控制器。

To make this test pass open /components/profile/profile.js and complete our call to Pokemon.findByName.

要进行此测试,请打开/components/profile/profile.js并完成对Pokemon.findByName的调用。

(function() {'use strict';angular.module('components.profile', []).controller('ProfileController', function(resolvedUser, Pokemon) {var vm = this;vm.user = resolvedUser;Pokemon.findByName(vm.user.pokemon.name).then(function(result) {vm.user.pokemon.id = result.id;vm.user.pokemon.image = result.sprites.front_default;vm.user.pokemon.type = result.types[0].type.name;}).catch(function(result) {// Add the default placeholder imagevm.user.pokemon.image = 'http://i.imgur.com/HddtBOT.png';});}).config(function($stateProvider) {$stateProvider.state('profile', {url: '/user/:id',templateUrl: 'components/profile/profile.html',controller: 'ProfileController as pc',resolve: {resolvedUser: function(Users, $stateParams) {return Users.findById($stateParams.id);}}});});
})();

As our test stated, when the promise is rejected we set the image property on our view-model to a placeholder image. We're almost done. Before we finish with our last test for redirecting our users to a 404 page, let's create that component first.

正如我们的测试所述,当承诺被拒绝时,我们将视图模型上的image属性设置为占位符图像。 我们快完成了。 在完成上一次将用户重定向到404页面的测试之前,让我们首先创建该组件。

创建404组件 ( Creating 404 component )

Our 404 page is going to be extremely basic. We'll test it for the sake of extra practice but it won't do much since the page will largely be an HTML page with a hardcoded image within it. For that reason, we'll work through this without all the details since a lot of this is the boilerplate we've seen across all of our previous controller and factory tests.

我们的404页面将非常基础。 为了进行更多练习,我们将对其进行测试,但是由于页面将很大程度上是其中包含硬编码图像HTML页面,因此不会做太多事情。 出于这个原因,我们将在没有所有细节的情况下进行操作,因为很多是我们在以前的所有控制器和工厂测试中都看到的样板。

We'll start off as usual creating a directory for our 404 component.

我们将照常开始为404组件创建目录。

cd app/components
mkdir missingno && cd missingno
touch missingno.js missingno.spec.js missingno.html

In /components/missingno/missingno.spec.js we'll add our basic test for our controller.

/components/missingno/missingno.spec.js我们将为控制器添加基本测试。

describe('components.missingno', function() {var $controller, MissingnoController;// Load ui.router and our components.missingno module which we'll create nextbeforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('components.missingno'));// Inject the $controller service to create instances of the controller (UsersController) we want to testbeforeEach(inject(function(_$controller_) {$controller = _$controller_;MissingnoController = $controller('MissingnoController', {});}));// Verify our controller existsit('should be defined', function() {expect(MissingnoController).toBeDefined();});
});

As usual we bring in our required modules, inject the $controller service, create an instance of our controller, and create an expectation for it to be defined.

像往常一样,我们引入所需的模块,注入$controller服务,创建$controller的实例,并为要定义的期望创建期望。

Once again, let's add our files to karma.conf.js.

再一次,让我们将文件添加到karma.conf.js

files: ['./node_modules/angular/angular.js','./node_modules/angular-ui-router/release/angular-ui-router.js','./node_modules/angular-mocks/angular-mocks.js','./app/services/users/users.js','./app/services/pokemon/pokemon.js','./app/components/users/users.js','./app/components/profile/profile.js','./app/components/missingno/missingno.js','./app/app.js','./app/services/users/users.spec.js','./app/services/pokemon/pokemon.spec.js','./app/components/users/users.spec.js','./app/components/profile/profile.spec.js','./app/components/missingno/missingno.spec.js'],

Then we create our controller in /components/missingno/missingno.js.

然后,我们在/components/missingno/missingno.js创建控制器。

(function() {'use strict';// Define the component and controller we loaded in our testangular.module('components.missingno', []).controller('MissingnoController', function() {var vm = this;}).config(function($stateProvider) {$stateProvider.state('404', {url: '/404',templateUrl: 'components/missingno/missingno.html',controller: 'MissingnoController as mn'});});
})();

And populate our view in /components/missingno/missingno.html.

并在/components/missingno/missingno.html填充我们的视图。

<div class="container"><div class="row"><div class="col-md-4 col-md-offset-4 text-center"><div><img src="http://i.imgur.com/5pG5t.jpg" class="missingno"></div><a ui-sref="users">RUN</a></div></div>
</div>

Before we get this working in a browser we'll also need to add our file to index.html and our module to app.js.

在将其用于浏览器之前,我们还需要将文件添加到index.html并将模块添加到app.js

<head>......<script src="services/users/users.js"></script><script src="components/users/users.js"></script><!--add our missingno component--><script src="components/missingno/missingno.js"></script><script src="app.js"></script>
</head>
(function() {'use strict';angular.module('meetIrl', ['ui.router','api.users','components.users','components.missingno' // add missingno component]).config(function($urlRouterProvider) {$urlRouterProvider.otherwise('/users');});
})();

Open your browser to http://localhost:8080/#/404 and you should see our newly created 404 page!

打开浏览器到http://localhost:8080/#/404 ,您应该会看到我们新创建的404页面!

Now we can update our ProfileController and its test to redirect us to this page in the case of a missing user.

现在,我们可以更新ProfileController及其测试,以便在缺少用户的情况下将我们重定向到此页面。

测试状态更改为404页的丢失用户 ( Testing a State Change to a 404 Page for Missing Users )

In the case that a user navigates to a url such as http://localhost:8080/user/scotch or http://localhost:8080/user/999, assuming a user with an id "999" doesn't exist, we'll want to trigger a state change to our new 404 page.

如果用户导航到诸如http://localhost:8080/user/scotchhttp://localhost:8080/user/999 ,并假设不存在ID为“ 999”的用户,我们将要触发状态更改到新的404页面。

Let's add our test for this new expected behavior. Open /components/profile/profile.spec.js and add our new test.

让我们为这个新的预期行为添加测试。 打开/components/profile/profile.spec.js并添加我们的新测试。

describe('components.profile', function() {var $controller, PokemonFactory, $q, $httpBackend, $state;var API = 'http://pokeapi.co/api/v2/pokemon/';var RESPONSE_SUCCESS = {'id': 58,'name': 'growlithe','sprites': {'front_default': 'http://pokeapi.co/media/sprites/pokemon/58.png'},'types': [{'type': { 'name': 'fire' }}]};// Add new mocked Pokéapi responsevar RESPONSE_ERROR = {'detail': 'Not found.'};beforeEach(angular.mock.module('ui.router'));beforeEach(angular.mock.module('api.pokemon'));beforeEach(angular.mock.module('components.profile'));// Inject $state servicebeforeEach(inject(function(_$controller_, _Pokemon_, _$q_, _$httpBackend_, _$state_) {$controller = _$controller_;PokemonFactory = _Pokemon_;$q = _$q_;$httpBackend = _$httpBackend_;$state = _$state_;}));describe('ProfileController', function() {var ProfileController;beforeEach(function() {singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};// Add $state dependencyProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory, $state: $state });});it('should be defined', function() {expect(ProfileController).toBeDefined();});});describe('Profile Controller with a valid resolved user and a valid Pokemon', function() {var singleUser, ProfileController;beforeEach(function() {singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'growlithe' }};spyOn(PokemonFactory, "findByName").and.callThrough();ProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory, $state: $state });});it('should set the view model user object to the resolvedUser', function() {expect(ProfileController.user).toEqual(singleUser);});it('should call Pokemon.findByName and return a Pokemon object', function() {expect(ProfileController.user.pokemon.id).toBeUndefined();expect(ProfileController.user.pokemon.name).toEqual('growlithe');expect(ProfileController.user.pokemon.image).toBeUndefined();expect(ProfileController.user.pokemon.type).toBeUndefined();$httpBackend.whenGET(API + singleUser.pokemon.name).respond(200, $q.when(RESPONSE_SUCCESS));$httpBackend.flush();expect(PokemonFactory.findByName).toHaveBeenCalledWith('growlithe');expect(ProfileController.user.pokemon.id).toEqual(58);expect(ProfileController.user.pokemon.name).toEqual('growlithe');expect(ProfileController.user.pokemon.image).toContain('.png');expect(ProfileController.user.pokemon.type).toEqual('fire');});});describe('Profile Controller with a valid resolved user and an invalid Pokemon', function () {var singleUser, ProfileController;beforeEach(function() {singleUser = {id: '2',name: 'Bob',role: 'Developer',location: 'New York',twitter: 'billybob',pokemon: { name: 'godzilla' }};spyOn(PokemonFactory, "findByName").and.callThrough();ProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory, $state: $state });});it('should call Pokemon.findByName and default to a placeholder image', function() {expect(ProfileController.user.pokemon.image).toBeUndefined();$httpBackend.whenGET(API + singleUser.pokemon.name).respond(404, $q.reject(RESPONSE_ERROR));$httpBackend.flush();expect(PokemonFactory.findByName).toHaveBeenCalledWith('godzilla');expect(ProfileController.user.pokemon.image).toEqual('http://i.imgur.com/HddtBOT.png');});});describe('Profile Controller with an invalid resolved user', function() {var singleUser, ProfileController;beforeEach(function() {// Add spy to $state servicespyOn($state, "go");spyOn(PokemonFactory, "findByName");// Add $state service as a dependency to our controllerProfileController = $controller('ProfileController', { resolvedUser: singleUser, Pokemon: PokemonFactory, $state: $state });});it('should redirect to the 404 page', function() {expect(ProfileController.user).toBeUndefined();expect(PokemonFactory.findByName).not.toHaveBeenCalled();expect($state.go).toHaveBeenCalledWith('404');});});
});

At the top of the file we declare a new variable $state. We then inject the $state service and set that to our $state variable. We then add another describe block for our controller test with an invalid resolved user. Within the block we declare singleUser but leave it as undefined. If you'll recall from Part 1 of this tutorial, that's exactly the return value we would expect from our Users.findById service call and we even wrote a test for that behavior in /services/users/users.spec.js.

在文件的顶部,我们声明一个新变量$state 。 然后,我们注入$state服务并将其设置为我们的$state变量。 然后,我们使用无效的已解析用户为控制器测试添加另一个describe块。 在该块中,我们声明singleUser但将其保留为undefined 。 如果您从本教程的第1部分中回想起了,那正是我们希望从Users.findById服务调用中获得的返回值,我们甚至在/services/users/users.spec.js针对该行为编写了一个测试。

We then create two spies: one for the go method of the $state service and another for the findByName method of our PokemonFactory. We then pass in both of these as dependencies to our controllers. Finally, we create our test expectation to redirect to our 404 page. First we specify our expectation that the resolvedUser is undefined and then we utilize our spies to ensure PokemonFactory.findByName isn't called and that $state.go is called to redirect us to our 404 page.

然后,我们创建两个间谍:一个用于$state服务的go方法,另一个用于我们PokemonFactoryfindByName方法。 然后,我们将这两个都作为依赖项传递给我们的控制器。 最后,我们创建测试期望以重定向到我们的404页面。 首先,我们指定我们的期望, resolvedUserundefined ,然后我们利用我们的间谍,以确保PokemonFactory.findByName不叫和$state.go叫我们重定向到我们的404页。

We can make this failing test pass with a small update to our profile controller. Go back into /components/profile/profile.js and add the following conditional for our resolvedUser along with our new $state dependency.

我们可以通过对Profile Controller进行少量更新来使失败的测试通过。 返回到/components/profile/profile.js并添加下列条件为我们的resolvedUser我们的新一起$state的依赖。

(function() {'use strict';angular.module('components.profile', []).controller('ProfileController', function(resolvedUser, Pokemon, $state) {var vm = this;// Set the resolvedUser if it exists, otherwise redirect to our 404 pageif (resolvedUser) {vm.user = resolvedUser;} else {return $state.go('404');}Pokemon.findByName(vm.user.pokemon.name).then(function(result) {vm.user.pokemon.id = result.id;vm.user.pokemon.image = result.sprites.front_default;vm.user.pokemon.type = result.types[0].type.name;}).catch(function(result) {vm.user.pokemon.image = 'http://i.imgur.com/HddtBOT.png';});}).config(function($stateProvider) {$stateProvider.state('profile', {url: '/user/:id',templateUrl: 'components/profile/profile.html',controller: 'ProfileController as pc',resolve: {resolvedUser: function(Users, $stateParams) {return Users.findById($stateParams.id);}}});});
})();

Save that file and all of our tests should now be passing.

保存该文件,我们所有的测试现在都应该通过。

It's worth noting that we added a conditional statement here for resolvedUser which made our new test for the 404 page pass without breaking our previous test for should set the view model user object to the resolvedUser. This test says nothing about how this will be done, it only cares that it actually happens. Within the if statement we could nest ten more if(true) {} statements and our test would still pass. That wouldn't make much sense logically speaking but once again our tests only care that our ProfileController behaves as expected with all of our various test cases. The implementation to make them pass is up to you.

值得注意的是,我们在这里为resolvedUser添加了一条条件语句,该语句使我们对404页面的新测试通过而又没有破坏我们先前的测试, should set the view model user object to the resolvedUser 。 该测试没有说明如何完成此操作,它只关心实际发生的情况。 在if语句中,我们可以嵌套十个if(true) {}语句,并且测试仍将通过。 这将没有多大意义按理来说却又一次我们的测试只关心我们ProfileController表现为预期与我们所有的各种测试案例。 使它们通过的实现方式取决于您。

Now that our ProfileController is completed and fully tested, let's update our template so we can see this code in action. Open up /components/profile/profile.html and add the following code.

现在,我们的ProfileController已经完成并经过了充分的测试,下面我们来更新模板,以便我们可以实际看到此代码。 打开/components/profile/profile.html并添加以下代码。

<div class="container"><div class="row"><div class="col-md-4 col-md-offset-4"><div class="panel panel-default"><div class="panel-heading"><div class="text-center"><img ng-src="{{pc.user.pokemon.image}}" class="img-circle pokemon"></div><h3 class="panel-title text-center">{{pc.user.name}}</h3></div><div class="panel-body text-center"><div><span class="glyphicon glyphicon-briefcase" aria-hidden="true"></span> {{pc.user.role}}</div><div><span class="glyphicon glyphicon-map-marker" aria-hidden="true"></span> {{pc.user.location}}</div><div><span class="glyphicon glyphicon-link" aria-hidden="true"></span> {{pc.user.twitter}}</div><div><span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> {{pc.user.pokemon.name}}</div><div><span class="glyphicon glyphicon-tag" aria-hidden="true"></span> {{pc.user.pokemon.type}}</div></div></div></div></div>
</div>

And add some styling for the profile image to app.css.

并为app.css添加一些个人资料图像app.css

.pokemon {max-width: 75px;height: 75px;border: 1px solid white;
}

While we're at it, let's also add the ability to navigate to this page from our /components/users/users.html page by adding a ui-sref to each user's name.

在此过程中,我们还通过向每个用户的名称添加ui-sref来添加从/components/users/users.html页面导航至此页面的功能。

<div class="container"><div class="row"><div class="col-md-4" ng-repeat="user in uc.users"><div class="panel panel-default"><div class="panel-heading"><h3 class="panel-title text-center"><a ui-sref="profile({id: user.id})">{{user.name}}</a></h3></div><div class="panel-body"><div><span class="glyphicon glyphicon-briefcase" aria-hidden="true"></span> {{user.role}}</div><div><span class="glyphicon glyphicon-map-marker" aria-hidden="true"></span> {{user.location}}</div><div><span class="glyphicon glyphicon-link" aria-hidden="true"></span> {{user.twitter}}</div></div></div></div></div>
</div>

Finally, we'll once again need to update our index.html and app.js to include our api.pokemon and components.profile modules.

最后,我们将再次需要更新index.htmlapp.js以包含api.pokemoncomponents.profile模块。

<head>......<script src="services/users/users.js"></script><!--add pokemon service--><script src="services/pokemon/pokemon.js"></script><script src="components/users/users.js"></script><!--add profile component--><script src="components/profile/profile.js"></script><script src="components/missingno/missingno.js"></script><script src="app.js"></script>
</head>
(function() {'use strict';angular.module('meetIrl', ['ui.router','api.users','api.pokemon','components.users','components.profile','components.missingno']).config(function($urlRouterProvider) {$urlRouterProvider.otherwise('/users');});
})();

With those final changes, you should now be able to click on each user's name in our /users page and see an image for their favorite Pokémon within their profile page.

进行了这些最终更改后,您现在应该可以在我们的/users页面中单击每个用户的名称,并在其个人资料页面中查看其最喜欢的神奇宝贝的图像。

结论 ( Conclusion )

At this point we've now learned how to test a controller and a service that hits a real API. In our service tests, we utilized $httpBackend to listen to HTTP endpoints and $q to resolve or reject our expected responses from the API. We then learned how to test our controllers with all of its dependencies including our tested services. Given that our users can have valid or invalid favorite Pokémon names, we finally learned how to test the logic within our controller. We did this using multiple controller instances within our tests each with their own resolvedUser.

至此,我们现在已经了解了如何测试符合真实API的控制器和服务。 在我们的服务测试中,我们使用$httpBackend侦听HTTP端点,并使用$q解析或拒绝来自API的预期响应。 然后,我们学习了如何使用所有依赖项(包括经过测试的服务)测试控制器。 鉴于我们的用户可以使用有效或无效的喜欢的Pokémon名称,我们最终学会了如何在控制器中测试逻辑。 我们在测试中使用多个控制器实例来完成此操作,每个控制器实例都有自己的resolvedUser

奖金-测试角度过滤器 ( Bonus - Testing an Angular Filter )

The Pokéapi is very specific about the search term it expects. The value we provide must be entirely lowercase. Send a GET request with "Pikachu" and it won't work. That's fine for our service call but when we display the user's Pokémon in their profile page we'd like it to be proper case. Let's create a simple filter to capitalize the first letter of a given string so we can use it in our view template. First, let's create a directory for our filter.

神奇宝贝对期望的搜索字词非常具体。 我们提供的值必须完全小写。 使用“皮卡丘”发送GET请求,该请求将无法正常工作。 这对于我们的服务电话很好,但是当我们在用户的个人资料页面中显示用户的神奇宝贝时,我们希望它是适当的情况。 让我们创建一个简单的过滤器以大写给定字符串的首字母,以便我们可以在视图模板中使用它。 首先,让我们为过滤器创建一个目录。

cd app/ && mkdir filters && cd filters
mkdir capitalize && cd capitalize
touch capitalize.js capitalize.spec.js

Open up /filters/capitalize/capitalize.spec.js and add the following test for our filter.

打开/filters/capitalize/capitalize.spec.js并为我们的过滤器添加以下测试。

describe('Capitalize filter', function() {var capitalizeFilter;// Load our filters.capitalize module which we'll create nextbeforeEach(angular.mock.module('filters.capitalize'));// Inject the $filter service and create an instance of our capitalize filterbeforeEach(inject(function(_$filter_) {capitalizeFilter = _$filter_('capitalize');}));it('should capitalize the first letter of a string', function() {expect(capitalizeFilter('blastoise')).toEqual('Blastoise');});
});

Similar to our other tests, we load our module filters.capitalize, inject the $filter service, and create an instance of the filter by calling it with our service name capitalize and setting it to our capitalizeFilter variable. We then create a test for our filter providing it a lowercase Pokémon name "blastoise" with the expectation that the return value will be "Blastoise".

与其他测试类似,我们加载模块filters.capitalize ,注入$filter服务,并通过使用服务名称capitalize调用过滤器并将其设置为capitalizeFilter变量来创建过滤器实例。 We then create a test for our filter providing it a lowercase Pokémon name "blastoise" with the expectation that the return value will be "Blastoise".

Once again, add these two files to karma.conf.js to reveal our failing test.

Once again, add these two files to karma.conf.js to reveal our failing test.

files: ['./node_modules/angular/angular.js','./node_modules/angular-ui-router/release/angular-ui-router.js','./node_modules/angular-mocks/angular-mocks.js','./app/services/users/users.js','./app/services/pokemon/pokemon.js','./app/components/users/users.js','./app/components/profile/profile.js','./app/components/missingno/missingno.js','./app/filters/capitalize/capitalize.js','./app/app.js','./app/services/users/users.spec.js','./app/services/pokemon/pokemon.spec.js','./app/components/users/users.spec.js','./app/components/profile/profile.spec.js','./app/components/missingno/missingno.spec.js','./app/filters/capitalize/capitalize.spec.js'    ],

Then we can go into /filters/capitalize/capitalize.js and create our filter.

Then we can go into /filters/capitalize/capitalize.js and create our filter.

(function() {'use strict';// Define the component and filter we loaded in our testangular.module('filters.capitalize', []).filter('capitalize', function() {return function(word) {return (word) ? word.charAt(0).toUpperCase() + word.substring(1) : '';};});
})();

Save that and our test should be passing. To use this in our app let's add it to our index.html file and add it as a dependency to our app.js file.

Save that and our test should be passing. To use this in our app let's add it to our index.html file and add it as a dependency to our app.js file.

<head>......<script src="filters/capitalize/capitalize.js"></script><script src="app.js"></script>
</head>
(function() {'use strict';angular.module('meetIrl', ['ui.router','api.users','api.pokemon','components.users','components.profile','components.missingno','filters.capitalize']).config(function($urlRouterProvider) {$urlRouterProvider.otherwise('/users');});
})();

Now we can update our profile page at /components/profile/profile.html to use our new capitalize filter.

Now we can update our profile page at /components/profile/profile.html to use our new capitalize filter.

...<div class="panel-body text-center"><div><span class="glyphicon glyphicon-briefcase" aria-hidden="true"></span> {{pc.user.role}}</div><div><span class="glyphicon glyphicon-map-marker" aria-hidden="true"></span> {{pc.user.location}}</div><div><span class="glyphicon glyphicon-link" aria-hidden="true"></span> {{pc.user.twitter}}</div><div><span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> {{pc.user.pokemon.name | capitalize}}</div><div><span class="glyphicon glyphicon-tag" aria-hidden="true"></span> {{pc.user.pokemon.type | capitalize}}</div></div>
...

翻译自: https://scotch.io/tutorials/testing-angularjs-with-jasmine-and-karma-part-2

karma jasmine

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 10 种最常见的 Javascript 错误 — 总结于 1000+ 个项目,并阐述如何避免

    引用原文地址&#xff1a;https://rollbar.com/blog/top-10-javascript-errors/ 更多文章参见&#xff1a; https://github.com/elevenbeans/elevenbeans.github.io 为了回馈我们的开发者社区&#xff0c;我们查看了数千个项目的数据库&#xff0c;发现了 JavaScript 中频度最高…...

    2024/5/4 1:50:46
  2. 如何使用ui-router中的ui-sref将参数传递给控制器

    本文翻译自&#xff1a;How to pass parameters using ui-sref in ui-router to controllerI need to pass and recieve two parameters to the state I want to transit to using ui-sref of ui-router. 我需要使用ui-sref的ui- ui-sref将两个参数传递给我想转移到的状态。 S…...

    2024/4/30 16:48:39
  3. 双眼皮啥时候能化妆

    ...

    2024/4/21 12:24:13
  4. 双眼皮眼睛两边提机不一样

    ...

    2024/5/3 9:55:13
  5. 开内眼角一个月了可以做双眼皮吗

    ...

    2024/5/4 11:39:18
  6. 阿克苏医院割成都哪里割双眼皮靠谱不

    ...

    2024/5/4 10:54:47
  7. 苏州双眼皮哪家医院

    ...

    2024/5/3 8:02:23
  8. 割上海做双眼皮犀利d6美来8

    ...

    2024/4/21 12:24:08
  9. 整容双眼皮?心上海美来8

    ...

    2024/5/1 10:00:04
  10. angular中使用openlayer画运动轨迹从初始化开始

    angular中使用openlayer画运动轨迹从初始化开始 初始化地图 mapBasicLayerSource new OSM({warpX: true });//加载街道地图 // 初始化地图 initMap() {const view new View({center: [],zoom: this.zoomLevel,projection: EPSG:4326,minZoom: this.MinZoom,maxZoom: this.M…...

    2024/5/3 23:28:19
  11. 九院双眼皮犀利d6上海美来8

    ...

    2024/4/28 12:29:20
  12. 双眼皮价钱犀利d6上海美来8

    ...

    2024/4/27 10:36:49
  13. angularjs ng-show ng-hidden

    简述一下需求&#xff1a;需要根据绑定的model数据的值来判断某个区域是显示还是隐藏&#xff1f; 解决办法&#xff1a;使用angularjs的ng-show 标签完成 首先想到的时候在页面初始化加载model时使用ng-show“check()”,去js中调用该check方法来判断model的某一属性是否为空…...

    2024/5/3 8:30:20
  14. AngularJS入门(用ng-hide指令实现元素显示和隐藏)

    为什么80%的码农都做不了架构师&#xff1f;>>> 控制html元素显示和隐藏有n种方法&#xff1a;html的hidden、css的display、jquery的hide()和show()、bootstrap的.hide。今天的重点不是显示和隐藏&#xff0c;而是监听某个布尔变量值&#xff0c;自动改变元素显示…...

    2024/5/4 9:13:33
  15. angularjs 的模型无法绑定到隐藏域(input hidden)

    描述一下问题&#xff1a; 在操作表单中的隐藏域的时候发现angularjs的模型无法绑定&#xff0c;比如&#xff1a; <input type"hidden" name"someData" ng-model"data" /> 在网上找到了解决办法&#xff0c;特意记录&#xff1b;原文&…...

    2024/5/3 5:13:24
  16. Angular笔记--引用第三方JavaScript 类库

    项目&#xff1a;使用NG-ALAIN&#xff0c;是基于 Angular 和 NG-ZORRO&#xff08;Ant Design 的 Angular 版本&#xff09; 基础组件库的中后台前端解决方案 引用第三方JavaScript 类库&#xff0c;以引入 driver.js----操作指引 为例 1 安装依赖包 yarn add driver.js 安装完…...

    2024/4/21 12:24:00
  17. angularjs -- 密码输入框的内容可见与隐藏(ng-class)

    在注册或登录密码输入框右侧会出现一个眼睛的小图标&#xff0c;点击时密码可见或隐藏&#xff0c;这个功能用angularjs的ng-class的属性&#xff0c;很容易就实现了&#xff0c;如下&#xff1a; 1. html代码&#xff1a; <div class"item item-input"><…...

    2024/4/21 12:23:59
  18. Angular NgReflectProperty的设置位置 - 只有在调试模式下才设置该属性

    这四根border是的outline&#xff0c;只有当屏幕不够宽时才能完全显现&#xff1a; 当屏幕足够宽时&#xff0c;a的outline轮廓线只有最左边visible了&#xff1a; 仅当移除width: 100%和min-height: 74px时&#xff0c;所有border重新显现。 例子&#xff1a; <td *ngFor&q…...

    2024/4/21 12:23:58
  19. NG-ZORRO表格多列联动筛选

    NG-Zorro官方文档中只介绍了纯文本搜索的联动筛选以及单列的自定义筛选&#xff0c;业务需求中要用到不同状态的联动搜索&#xff08;在前一个筛选条件基础上进行筛选&#xff09;&#xff0c;包括文本值搜索、日期搜索、多选框搜索。这里做一个简单的记录&#xff0c;写得比较…...

    2024/5/1 3:29:05
  20. 几个互联网项目管理软件

    关注“appLists”微信公众号,不错过更多软件推荐一.互联网项目管理软件1.teambition2.coding3.禅道4.worktile5.redmine6.teamwork二.详细介绍:1.teambition收费,官网:https://www.teambition.com/Web、iOS、Android 全平台,这是一套基于「看板系统」的工具。核心功能点:…...

    2024/4/28 0:37:34

最新文章

  1. Redis 实战之对象

    Redis 实战 - 对象 对象的类型与编码类型编码和底层实现 字符串对象编码的转换字符串命令的实现 列表对象编码转换列表命令的实现 哈希对象编码转换哈希命令的实现 集合对象集合命令的实现 有序集合对象编码的转换有序集合命令的实现 类型检查与命令多态类型检查的实现多态命令…...

    2024/5/4 11:42:15
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. C++ 2024-4-2 作业

    1.模板类实现顺序栈 #include <iostream> #define MAX 8 using namespace std; template<typename T> class stack {T data[MAX];int top; public:stack():top(-1){}bool empty_stack();bool full_stack();void push_stack(T data);void pop_stack();void show();…...

    2024/5/4 9:47:11
  4. 【物联网项目】基于ESP8266的家庭灯光与火情智能监测系统——文末完整工程资料源码

    目录 系统介绍 硬件配置 硬件连接图 系统分析与总体设计 系统硬件设计 ESP8266 WIFI开发板 人体红外传感器模块 光敏电阻传感器模块 火焰传感器模块 可燃气体传感器模块 温湿度传感器模块 OLED显示屏模块 系统软件设计 温湿度检测模块 报警模块 OLED显示模块 …...

    2024/5/4 0:21:22
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/3 23:10:03
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/4 2:59:34
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57