<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プロパティを提供するカスタマイズされた変更をディスパッチすることによって別のアプローチを開始しましたが、それでも成功しませんでした。
短い話ですが、このテストケースへの取り組み方に関する実用的なソリューションやベストプラクティスを学びたいと思っています。
AngularJSを再考しましょう、DOM must be handled in a directive
特にテストの目的で、コントローラー、つまりelement.on('change', ..
のDOM要素を処理するべきではありません。コントローラーでは、DOMではなくデータとやり取りします。
したがって、これらのonchange
は次のようなディレクティブである必要があります
<input type="file" name='file' ng-change="fileChanged()" /> <br/>
ただし、残念ながらng-change
はtype="file"
ではうまく機能しません。将来のバージョンがこれで動作するかどうかはわかりません。ただし、同じ方法を適用できます。
<input type="file"
onchange="angular.element(this).scope().fileChanged(this.files)" />
コントローラでは、メソッドを定義するだけです
$scope.fileChanged = function(files) {
return files.0.length < 500000;
};
さて、すべては単なる通常のコントローラーテストです。 angular.element
、$compile
、triggers
などを扱う必要はもうありません! :)
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);
}
));
});
UPDATE:@PeteBDのおかげで、
Angularjsバージョン1.2.22以降、jqLiteはtriggerHandler()
へのカスタムイベントオブジェクトの受け渡しをサポートするようになりました。参照: d262378b
jqLiteのみを使用している場合、
triggerHandler()
は、ダミーイベントオブジェクトをハンドラーに渡すため、機能しません。
ダミーイベントオブジェクトは次のようになります( jqLite.js#L962 からコピー)
{
preventDefault: noop,
stopPropagation: noop
}
ご覧のとおり、target
プロパティもありません。
jQueryを使用している場合、
次のようなカスタムイベントオブジェクトでイベントをトリガーできます。
input.triggerHandler({
type: 'change',
target: {
files: fileList
}
});
そしてその evt.target.files
は、予想どおりfileList
になります。
お役に立てれば。
次に、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);
})