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.
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 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});
.$timeout
, $interval
, $log
, and $exceptionHandler
.You can read more about ngMock at https://docs.angularjs.org/api/ngMock.
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
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
.
$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.
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:
$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.18.223.106.232