将所有第三方服务封装成Service
  一个Web项目中总是无法避免地要使用一些第三方的服务, 这里讨论的主要是前端的一些第三方服务, 比如在线客服, 站点统计等, 这些代码都在我们的控制之外, 大多数时候下都是从服务提供商的服务器上下载下来的, 而我们需要在业务代码中调用这些代码.如果我们每次都是赤裸裸地以全局变量的形式来使用这些服务, 那么造成的问题是这样的代码很难测试, 因为这些代码是不存在于我们的代码库中的, 而且内容应该也是不定时更新的, 大多数情况很多人会因为这些原因放弃到对这类操作的测试.假设我们现在需要在某些动作发生之后调用一个第三方服务, 这个第三方服务叫做serviceLoadedFromExternal, 它提供了一个API叫做makeServiceCall, 如果直接使用这个API, 那么在测试中很难去验证这个服务被执行了(因为在单元测试环境中这个服务根本不存在), 但是如果我们将这个服务包装成一个angular service, 那么可以在测试中轻易地将它替换成一个mock对象, 然后验证这个mock对象上的方法被调用了可以了.比较下面的两段代码:
  直接使用第三方服务
  angular.module(“TheNG”, [])
  .controller(“AController”, function ($scope) {
  $scope.someAction = function () {
  // handle some logic here
  serviceLoadedFromExternal.makeServiceCall();
  };
  });
  使用封装成service的第三方服务
  angular.module(“TheNG”, [])
  .factory(“wrappedService”, function () {
  return {
  makeServiceCall: function () {
  serviceLoadedFromExternal.makeServiceCall();
  }
  };
  })
  .controller(“AController”, function ($scope, wrappedService) {
  $scope.someAction = function () {
  // handle some logic here
  wrappedService.makeServiceCall();
  };
  });
  Angular是高度模块化的, 它希望通过这种模块的形式来解决JS代码管理上的混乱, 并且使用依赖注入来自动装配, 这一点与Spring IOC很像, 带来的好处是你的依赖是可以随意替换的, 这极大的增加了代码的可测试性.
  尽量将Ajax请求放到service中去做
  Angular中使用service来组织那些可被复用的逻辑, 除此之外, 我们也可以将service理解为是对应一个领域对象的操作的集合, 因此, 通常会将一组Ajax操作放在一个service中去统一管理.
  当然了,你也可以通过向你的directive或是controller中注入$http, 但是我个人不喜欢这种做法. 首先,$http是一个比较初级的依赖, 与其实注入的业务服务不是一个抽象层级, 如果在你的业务代码中直接操作http请求, 给人的一种感觉像是在Spring MVC的request method中直接使用HttpServeletRequest一样, 有点突兀, 另外会让整个方法失衡, 因为这些操作的抽象层次是不一样的. 其次是给测试带来的麻烦, 我们不得不使用$httpBackend来模拟一个HTTP请求的发送.
  我们应该设法让测试更简单,通过将Ajax请求封装到service中, 我们只需要让被mock的service返回我们期望的结果可以了. 只有这样大家才会喜欢写测试, 甚至是做到测试驱动开发, 要去mock$http这样的东西, 显然是增加了测试的负担.
  使用Promise处理Ajax的返回值, 而不是传递回调函数
  Angular中所有的Ajax请求默认都返回一个Promise对象, 不建议将处理Ajax返回值的逻辑通过回调函数的形式传递给发送http请求的service, 而应该是在调用service的地方利用返回的promise对象来决定如何处理.
  让我们通过下面的例子来感受一下:
  angular.module(“TheNG”, [])
  .service(“deliveryService”, function ($http) {
  this.validateAddress = function (address, success, failure) {
  $http.post(‘/unknown’, address).then(success, failure);
  };
  })
  .controller(“DeliveryController”, function ($scope, deliveryService) {
  var acceptAddress = function () {
  console.log(‘address accepted’);
  };
  var rejectAddress = function () {
  console.log(‘address rejected’);
  };
  $scope.validateAddress = function () {
  deliveryService.validateAddress($scope.address, acceptAddress, rejectAddress);
  };
  });
  这里的处理办法是将快递地址验证失败或成功之后的处理函数都传给了deliveryService, 当验证结果从服务器端返回之后, 相应的处理函数会被执行. 这做写法其实是比较常见的, 但是问题出在哪里呢?
  其实, 作为一个service的接口,validateAddress应该只接收一个待验证的地址, 验证完成之后返回一个验证结果可以了, 本来应该是一个很干净的接口, 我们之所以丑陋把对应的处理函数也传进去, 原因在于这是一个异步的请求, 所以需要在发请求的时候将对处理函数绑定上去.
  你应该已经猜到了第二个问题我会说一说对它的测试, 通常来说, 如果一个service创建成本较高或是存在外部依赖/请求的话, 我们会将这个service mock掉, 通过让mocked service直接返回我们想要的结果来让我们只关注被验证的业务逻辑.我们回头看一下上面的实现, 如果我们把deliveryService的validateAddress()方法mock掉, 那么我们根本没有办法去验证acceptAddress()和rejectAddress()里面的逻辑!
  所以, 如果你的处理函数是传递给service中的API的话, 那么你的测试其实已经跟这个API的实现绑定了, 你只有去创建一个真实的service并且让它发送HTTP请求, 你的处理函数才会被执行到.
  经过这一番折腾, 你一定要说, 这测试比实现代码难写多了. 正确的打开方式应该是这样的: service的API只需要返回promise, 对应的处理函数的绑定在这个返回的promise上, 这样我们只需要mock那个service的接口让它返回一个我们期望的promise, 然后控制promise的结果让对应的处理函数被执行:
  angular.module(“TheNG”, [])
  .service(“deliveryService”, function ($http) {
  this.validateAddress = function (address) {
  return $http.post(‘/unknown’, address);
  };
  })
  .controller(“DeliveryController”, function ($scope, deliveryService) {
  var acceptAddress = function () {
  console.log(‘address accepted’);
  };
  var rejectAddress = function () {
  console.log(‘address rejected’);
  };
  $scope.validateAddress = function () {
  deliveryService.validateAddress($scope.address).then(acceptAddress, rejectAddress);
  };
  });
  本来打算接下来介绍一下Angular代码的单元测试的各种模式的, 写着写着篇幅有点多了, 期待下一篇吧.