web-dev-qa-db-ja.com

ユニットテスト用に<input type = 'file'>のイベントを変更するモックファイルを提供する方法

<input type='file'>を介してビューで通常選択されるファイルの処理を確認する単体テストで問題が発生しています。

私のAngularJSアプリのコントローラー部分では、ファイルは次のように入力の変更イベント内で処理されます。

//bind the change event of the file input and process the selected file
inputElement.on("change", function (evt) {
    var fileList = evt.target.files;
    var selectedFile = fileList[0];
    if (selectedFile.size > 500000) {
        alert('File too big!');
    // ...

単体テストでユーザーが選択したファイルではなく、モックデータをevt.target.filesに含めたいです。 FileListおよびFileオブジェクトを自分でインスタンス化することはできないことに気付きました。これは、ブラウザーが使用しているオブジェクトと一致します。そこで、モックFileListを入力のfilesプロパティに割り当て、変更イベントを手動でトリガーしました。

describe('document upload:', function () {
    var input;

    beforeEach(function () {
        input = angular.element("<input type='file' id='file' accept='image/*'>");
        spyOn(document, 'getElementById').andReturn(input);
        createController();
    });

    it('should check file size of the selected file', function () {
        var file = {
            name: "test.png",
            size: 500001,
            type: "image/png"
        };

        var fileList = {
            0: file,
            length: 1,
            item: function (index) { return file; }
        };

        input.files = fileList; // assign the mock files to the input element 
        input.triggerHandler("change"); // trigger the change event

        expect(window.alert).toHaveBeenCalledWith('File too big!');
    });

残念ながら、これによりコントローラーで次のエラーが発生し、ファイルがinput要素にまったく割り当てられなかったため、この試行が失敗したことが示されます。

TypeError: 'undefined' is not a object(evaluating 'evt.target.files')

セキュリティ上の理由から、input.filesプロパティがread-onlyであることはすでに知っています。それで、filesプロパティを提供するカスタマイズされた変更をディスパッチすることによって別のアプローチを開始しましたが、それでも成功しませんでした。

短い話ですが、このテストケースへの取り組み方に関する実用的なソリューションやベストプラクティスを学びたいと思っています。

20
Mobiletainment

AngularJSを再考しましょう、DOM must be handled in a directive

特にテストの目的で、コントローラー、つまりelement.on('change', ..のDOM要素を処理するべきではありません。コントローラーでは、DOMではなくデータとやり取りします。

したがって、これらのonchangeは次のようなディレクティブである必要があります

<input type="file" name='file' ng-change="fileChanged()" /> <br/>

ただし、残念ながらng-changetype="file"ではうまく機能しません。将来のバージョンがこれで動作するかどうかはわかりません。ただし、同じ方法を適用できます。

<input type="file" 
  onchange="angular.element(this).scope().fileChanged(this.files)" />

コントローラでは、メソッドを定義するだけです

$scope.fileChanged = function(files) {
  return files.0.length < 500000;
};

さて、すべては単なる通常のコントローラーテストです。 angular.element$compiletriggersなどを扱う必要はもうありません! :)

describe(‘MyCtrl’, function() {
  it('does check files', inject(
    function($rootScope, $controller) {
      scope = $rootScope.new();
      ctrl = $controller(‘UploadCtrl’, {‘$scope’: scope});

      var files = { 0: {name:'foo', size: 500001} };
      expect(scope.fileChanged(files)).toBe(true);
    }
  ));
});

http://plnkr.co/edit/1J7ETus0etBLO18FQDhK?p=preview

9
allenhwkim

UPDATE:@PeteBDのおかげで、

Angularjsバージョン1.2.22以降、jqLit​​eはtriggerHandler()へのカスタムイベントオブジェクトの受け渡しをサポートするようになりました。参照: d262378b


jqLit​​eのみを使用している場合、

triggerHandler()は、ダミーイベントオブジェクトをハンドラーに渡すため、機能しません。

ダミーイベントオブジェクトは次のようになります( jqLit​​e.js#L962 からコピー)

{
  preventDefault: noop,
  stopPropagation: noop
}

ご覧のとおり、targetプロパティもありません。

jQueryを使用している場合、

次のようなカスタムイベントオブジェクトでイベントをトリガーできます。

input.triggerHandler({
  type: 'change',
  target: {
    files: fileList
  }
});

そしてその evt.target.filesは、予想どおりfileListになります。

お役に立てれば。

11
runTarm

次に、angular2 +を使用した入力ファイル/画像の仕様例を示します。

it('should call showError on toastService Api on call of onSaveOfImage() method', () => {

    spyOn(component.commonFacade.fileIOApi, 'uploadFile');
    let file = new File([new ArrayBuffer(2e+5)], 'test-file.jpg', { lastModified: null, type: 'image/jpeg' });
    let fileInput={ files: [file] };
    component['onSaveOfImage'](fileInput,"",null,"","");
    expect(component.commonFacade.fileIOApi.uploadFile).toHaveBeenCalledTimes(1);
    expect(component.uploadedFileData).toBeUndefined();
    expect(component.commonFacade.employeeApi.toastService.showError).toHaveBeenCalledTimes(1);
  })
3