【问题标题】:AngularJS - Unit testing file uploadsAngularJS - 单元测试文件上传
【发布时间】:2013-09-30 22:23:04
【问题描述】:

如您所知,在单元测试中,它的内置 angularjs 功能可以使用 $httpBackend 模拟 XHR 请求 - 这在编写单元测试时非常有用。

最近遇到文件上传需要mock XHR,发现了一些问题。

考虑以下代码:

var xhr = new XMLHttpRequest();

xhr.upload.addEventListener("progress", uploadProgress(event), false);
xhr.addEventListener("load", uploadComplete(event), false);
xhr.addEventListener("error", uploadError(event), false);
xhr.addEventListener("abort", uploadAbort(event), false);
xhr.open("POST", 'some url');
xhr.send(someData);

我想做的是通过模拟 XHR 请求对这样的代码进行单元测试,但是因为这里没有使用 $http 服务,所以不可能这样做。

我试过这个(它正在工作,可以用 $httpBackend 模拟):

$http({
    method: 'POST', 
    url: 'some url', 
    data: someData, 
    headers: {'Content-Type': undefined},
    transformRequest: angular.identity})
.then(successCallback, errorCallback);

但在这种情况下,我不知道如何实现“进度”回调和“中止”回调(如果我现在正在工作,它们是必不可少的并且是必需的)。

我看到最新的 Angular 支持 Promise 的进度回调(不确定是否与 $http 服务集成),但是中止回调呢?

有什么想法或者你以前遇到过类似的事情吗?

【问题讨论】:

    标签: unit-testing testing angularjs mocking


    【解决方案1】:

    如果$http 服务没有为您提供所需的一切,您仍然可以对第一个代码块进行单元测试。首先,更改您的代码以使用 Angular 的 $window 服务。这只是一个包装服务,但它允许您在测试中模拟对象。所以,你会想要这样做:

    var xhr = new $window.XMLHttpRequest();
    

    然后在你的测试中,只是模拟它并使用间谍。

    $window.XMLHttpRequest= angular.noop;
    addEventListenerSpy = jasmine.createSpy("addEventListener");
    openSpy = jasmine.createSpy("open");
    sendSpy = jasmine.createSpy("send");
    xhrObj = {
       upload: 
       {
           addEventListener: addEventListenerSpy
       },
       addEventListener: addEventListenerSpy,
       open: openSpy,
       send: sendSpy
    };
    spyOn($window, "XMLHttpRequest").andReturn(xhrObj);
    

    从那里,你可以让不同的间谍返回你想要的任何东西来进行不同的测试。

    【讨论】:

    • 这非常有帮助......而且非常明显!
    【解决方案2】:

    您应该模拟 $http 并控制任何延迟,因为您希望更好地控制您的测试。基本上,模拟 $http 提供程序并提供一个自定义实现来公开其延迟,然后使用它。

    您不必担心$http 是否正常工作,因为它应该可以,并且已经过测试。所以你必须模拟它,只需要担心测试你的代码部分。

    你应该这样做:

    describe('Testing a Hello World controller', function() {
      beforeEach(module(function($provide) {
        $provide.provider('$http', function() {
          this.$get = function($q) {
            return function() {
              var deferred = $q.defer(),
                  promise = deferred.promise;
    
              promise.$$deferred = deferred;
              return promise;
            }
          };
        });
      }));
    
      it('should answer to fail callback', inject(function(yourService, $rootScope) {
        var spyOk = jasmine.createSpy('okListener'),
            spyAbort = jasmine.createSpy('abortListener'),
            spyProgress = jasmine.createSpy('progressListener');
    
        var promise = yourService.upload('a-file');
        promise.then(spyOk, spyAbort, spyProgress);
    
        promise.$$deferred.reject('something went wrong');
        $rootScope.$apply();
    
        expect(spyAbort).toHaveBeenCalledWith('something went wrong');
      }));
    });
    

    您的服务很简单:

    app.service('yourService', function($http) {
      return {
        upload: function(file) {
          // do something and
          return $http({...});
        }
      };
    });
    

    请注意,承诺通知仅在最新的 RC 版本中可用。因此,如果您不能使用它,只需详细说明示例并模拟 XHR 事件等等。

    还请注意,您最好为每个回调(失败、成功和进度)设置一个测试用例,以遵循 KISS 原则。

    【讨论】:

    • 谢谢,这是另一种变体,但从我的角度来看,dnc253 的解决方案看起来更清晰。
    • 顺便说一句,这是什么 - 'promise.$$deferred = deferred;'是什么意思?
    • 我只是暴露了 deferred,这样你就可以控制是解决、中止还是通知 promise。如果您确实打算直接使用 XHR 而不是使用 Angular $http,那么 dnc253 的答案肯定会更好。
    • 为了澄清那些没有看到使用原始 XHR 与 $http 的用例的人,某些较新的设施,如 xhr.upload.onprogress 尚未公开。这是 Angular 1.2 的版本,我还没有真正深入了解任何更新的东西
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-11
    • 2019-10-21
    • 2021-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多