首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在AngularJS指令单元测试中等待回调执行?

如何在AngularJS指令单元测试中等待回调执行?
EN

Stack Overflow用户
提问于 2015-04-08 02:46:17
回答 3查看 3.2K关注 0票数 0

我有一个简单的指令,它可以很好地工作(用TypeScript编写,但这并不重要),它允许通过<input type="file">和HTML5 File API获得上传的文件的内容。

用法很简单:

代码语言:javascript
复制
<body ng-controller="MyCtrl as ctrl">
  <input type="file" file-read="ctrl.readInputFile(data)">
</body>

正在使用文件内容调用回调readInputFile()。

代码语言:javascript
复制
interface IFileReadScope extends angular.IScope {
  fileRead(data: any): void;
}

class FileRead implements angular.IDirective {
  restrict = 'A';
  scope = {
    fileRead: '&'
  };

  link = (scope: IFileReadScope, element: angular.IAugmentedJQuery) => {
    element.on('change', (e: Event) => {
      const files: FileList = (<HTMLInputElement> e.target).files;

      for (let i = 0; i < files.length; i++) {
        const file = files[i];

        const reader = new FileReader();

        reader.onload = (e: Event) => {
          scope.$apply(() => {
            scope.fileRead({data: (<FileReader> e.target).result});
          });
        };

        reader.readAsText(file);
      }
    });
  };

  static factory(): angular.IDirectiveFactory {
    const directive = () => new FileRead();
    return directive;
  }
}

app.directive('fileRead', FileRead.factory());

下面是对它的测试:

代码语言:javascript
复制
describe('fileRead', () => {
  beforeEach(module('app'));

  let $compile: angular.ICompileService;
  let scope: any;

  beforeEach(inject((_$compile_, _$rootScope_) => {
    $compile = _$compile_;
    scope = _$rootScope_.$new();
  }));

  function compileTemplate(template: string): angular.IAugmentedJQuery {
    const el = $compile(angular.element(template))(scope);
    scope.$digest();
    return el;
  }

  it('should call a callback each time a file has been read', () => {
    const el = compileTemplate(
      '<input type="file" file-read="readInputFile(data)">'
    );

    const files = new Array<string>();

    scope.readInputFile = (data: any) => {
      files.push(data);
    };

    el.triggerHandler({
      type: 'change',
      target: {
        files: [
          new Blob(['data0']),
          new Blob(['data1'])
        ]
      }
    });

    scope.$digest();

    // Fails because it does not wait for scope.readInputFile() to be called
    expect(files.length).toEqual(2);
    expect(files[0]).toEqual('data0');
    expect(files[1]).toEqual('data1');
  });
});

问题是我不知道如何等待scope.readInputFile()回调被执行。

我尝试过作用域。$digest(),$rootScope.$digest(),$apply,Jasmine spyOn...我还检查了流行指令是如何做到这一点的。似乎没有人会这样做:/

有什么想法吗?

编辑:

感谢Hooray I和Matho的帮助,这里是使用Jasmine 2的两个可能的解决方案:

代码语言:javascript
复制
let files: string[];

beforeEach(done => {
  const el = compileTemplate(
    '<input type="file" file-read="readInputFile(data)">'
  );

  files = new Array<string>();

  scope.readInputFile = (data: any) => {
    files.push(data);
    if (files.length === 2) done();
  };

  el.triggerHandler({
    type: 'change',
    target: {
      files: [
        new Blob(['data0']),
        new Blob(['data1'])
      ]
    }
  });

  scope.$digest();
});

it('should call a callback each time a file has been read', () => {
  expect(files.length).toEqual(2);
  expect(files[0]).toEqual('data0');
  expect(files[1]).toEqual('data1');
});

在这种情况下,更优雅的是:

代码语言:javascript
复制
it('should call a callback each time a file has been read', done => {
  const el = compileTemplate(
    '<input type="file" file-read="readInputFile(data)">'
  );

  const files = new Array<string>();

  scope.readInputFile = (data: any) => {
    files.push(data);
    if (files.length === 2) {
      expect(files[0]).toEqual('data0');
      expect(files[1]).toEqual('data1');
      done();
    }
  };

  el.triggerHandler({
    type: 'change',
    target: {
      files: [
        new Blob(['data0']),
        new Blob(['data1'])
      ]
    }
  });

  scope.$digest();
});

如果scope.fileRead()没有在指令中执行,那么测试将失败:

代码语言:javascript
复制
FAILED fileRead should call a callback each time a file has been read
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

因此,如果代码中断,单元测试将失败,=>作业已完成:)

EN

回答 3

Stack Overflow用户

发布于 2015-04-08 04:01:00

我不使用Jasime,我总是使用mocha + chaijs + sinon,但是jasmine也实现了done函数,它应该是这样工作的:

代码语言:javascript
复制
it('async method', function(done){ 
//once we add the done param, karma will wait until the fn is executed

 service.doSomethingAsync()
  .then(function(){
    //assertions 
    // expect(result).to.be.something();
    done(); //Where we are telling karma that it can continue with the test
  }
  //Other assertions    
}

使用chai-as-promised

代码语言:javascript
复制
it('async method', function(done){

 var result = service.doSomethingAsync();
 expect(result).to.eventually.be(something=;
  //Other assertions    
}
票数 1
EN

Stack Overflow用户

发布于 2015-04-08 08:07:19

您使用的是哪个版本的Jasmine?

如果是1.3,您可以使用waitsFor and runs等待一段时间,然后在规范上断言:

所以

代码语言:javascript
复制
let files = new Array<string>();

scope.readInputFile = (data: any) => {
  files.push(data);
};

el.triggerHandler({
  type: 'change',
  target: {
    files: [
      new Blob(['data0']),
      new Blob(['data1'])
    ]
  }
});

scope.$digest();

变成了

代码语言:javascript
复制
let files = new Array<string>();

runs(function() {
    scope.readInputFile = (data: any) => {
        files.push(data);
    };

    el.triggerHandler({
        type: 'change',
        target: {
            files: [
                new Blob(['data0']),
                new Blob(['data1'])
            ]
        }
    });

    scope.$digest();
});

代码语言:javascript
复制
expect(files.length).toEqual(2);
expect(files[0]).toEqual('data0');
expect(files[1]).toEqual('data1');

变成了

代码语言:javascript
复制
waitsFor(function() {
    expect(files.length).toEqual(2);
    expect(files[0]).toEqual('data0');
    expect(files[1]).toEqual('data1');
}, "failure message", time_in_milliseconds);

如果你使用的是Jasmine 2.0,你可以使用done函数:

您可以将尝试测试的代码(以及它所依赖的任何设置代码)移动到beforeEach,在那里调用异步代码,然后在异步代码完成时通过调用done方法通知框架异步操作已经完成。在done调用后,您的规范将自动运行。我以前从来没有用过这个(我还在1.3版本上),我也没有使用Angular和Karma,所以我有点不熟悉。

根据this answer的说法,您的beforeEach函数应该如下所示:

代码语言:javascript
复制
beforeEach(function(done) {
    inject((_$compile_: angular.ICompileService, _$rootScope_: angular.IRootScopeService) => {
        $compile = _$compile_;
        $rootScope = _$rootScope_;
        scope = $rootScope.$new();
    });
    let el = compileTemplate('<input type="file" file-read="readInputFile(data)">');
    let files = new Array<string>();

    scope.readInputFile = (data: any) => {
        files.push(data);
    };

    el.triggerHandler({
        type: 'change',
        target: {
            files: [
                new Blob(['data0']),
                new Blob(['data1'])
            ]
        }
    });

    scope.$digest();

    done();
});

你的断言应该看起来像这样:

代码语言:javascript
复制
it('should call a callback each time a file has been read by FileReader', (done) => {
    expect(files.length).toEqual(2);
    expect(files[0]).toEqual('data0');
    expect(files[1]).toEqual('data1');

    done();
});

同样,我没有使用angular、TypeScript或Karma,因此您可能需要对此进行调整。但这是总体思路;设置一切,运行异步代码,在设置中调用done,运行断言,在规范中调用done

票数 1
EN

Stack Overflow用户

发布于 2015-04-08 02:54:24

你可以把你的expect操作放在回调函数中。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29498918

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档