Testing RESTful APIs in AngularJS

Testing is crucial to ensure the quality of any software, and this e-commerce app is no exception. The developers can either waste most of the development time chasing bugs, or investing that time in what really matters: algorithms, business logic, and UX/UI improvements. We are going to use three tools to test our client-side code: Karma and Mocha/Chai/SinonJS for unit testing, and Protractor for end-to-end testing.

Unit testing

In the previous chapter, we implemented some unit testing for Angular and for Node controllers and models.

We are going to continue our unit testing/implementation with the Product service. As we saw in the previous chapters, the service/factory is the module in charge of retrieving, saving, updating, and deleting the Product data (CRUD-ing products) from the database. In the service unit test, we just test the factory logic with no dependencies on other parts such as a database or RESTful API. For that, we are going to send mock HTTP calls with $httpBackend, and inject data into the controllers using ngMock.

ngMock

ngMock is a mocking module from the AngularJS core that helps us in injecting variables into the tests and mock AngularJS services. It also provides the ability to inspect them. Some of the services that ngMock provides are:

  • $httpBackend: This is a fake HTTP backend which can reply to requests with predefined responses. For example, $httpBackend.expectGET('/products').respond({title: 'book'}).
  • $controller: This is useful for testing controllers and directives. For example, var instanceController = $controller('ProductsCtrl', {$scope: scope});.
  • Other mock services: $timeout, $interval, $log, and $exceptionHandler.

Tip

You can read more about ngMock at https://docs.angularjs.org/api/ngMock.

Setting up testing

All the tests/implementations that we are going to develop are under meanshop/client/app/products. We will now test the Product Factory. Previously, in Chapter 2, Building an Amazing Store Frontend with AngularJS, we implemented a temporary factory that stores the data in memory. This time we are going to implement a Factory that uses REST to communicate with the ExpressJS web server and database. Later, we are going to unit test the Product Controller. There's no need to test the Product Factory again in the controller's unit tests. That is because, the Factory methods are mocked with the Jasmine spies in the controller's unit tests. Finally, we are going to do end-to-end tests using Protractor.

The commands that we are going to use are the following. Run all client and server unit tests:

grunt test

Before running e2e, we need to update the protractor web driver if we have not yet done so, by running the following command:

node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update

Also, let's create a new directory under e2e folder; create the product spec:

$ mkdir -p e2e/products
$ touch e2e/products/product{s.spec,.po}.js

Finally, we can run all e2e tests:

grunt test:e2e

Understanding the Services tests

The first action is to get a list of the products. We need to make an AJAX GET request to /api/products. Copy the content of https://github.com/amejiarosario/meanshop/blob/ch5/client/app/products/products.service.spec.js to your products.service.spec.js file. Let us now explain each element:

First, let's focus on the #index test. Most of the tests will look the same: we expect an $http call with a specific URL and HTTP verb to be made. In this case, the URL is /api/products and the verb is GET. The real $http is synchronous; however, the $httpBackend is not for easing the testing scenarios. It uses $httpBackend.flush() to check the replies to the HTTP requests synchronously.

Run grunt test:client.

Testing all $resource methods

$resource uses the $http service behind the scenes to make the calls to a REST endpoint. It provides a good starting number of methods, but we can extend our own ones like in the update method.

Let's continue taking a look at the #update tests. This time we pass a parameter with the ID of 123 to fetch the product. Later, we pass updated_attributes, which simulates the new products' data and expects it back. We use expectPUT to match the verb. Also, expectPUT and expectDELETE are available.

Run the tests again, and verify that everything gets passed. If we had not previously added the update method to $resource, this last test would not pass.

The delete, create, and show tests are very similar, and the full test suite can be viewed at https://raw.githubusercontent.com/amejiarosario/meanshop/ch5/client/app/products/products.service.spec.js.

Testing the Product Controller

Wait a moment! This controller uses our previously tested Product service. So, should we mock the HTTP requests as we did in the service test, or should we mock the service methods? Yes, since we've already tested the service, we can safely mock it in the controllers' tests at https://raw.githubusercontent.com/amejiarosario/meanshop/ch5/client/app/products/products.controller.spec.js.

Some highlights:

  • In AngularJS, all controller's scope inherit from $rootScope. We can create sub-scopes using the $new method. We create a new scope on each controller, and pass it using the $controller function.
  • We mock the Products service with SinonJS. Check out http://sinonjs.org/ for full details.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.106.232