web-dev-qa-db-fra.com

Comment fournir des fichiers fictifs pour modifier l'événement de <input type = 'file'> pour les tests unitaires

Je rencontre des difficultés avec un test unitaire dans lequel je souhaite vérifier le traitement d'un fichier, qui serait généralement sélectionné dans la vue via <input type='file'>.

Dans la partie contrôleur de mon application AngularJS, le fichier est traité à l'intérieur de l'événement de modification de l'entrée comme suit:

//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!');
    // ...

J'aimerais evt.target.files pour contenir mes données factices au lieu du fichier sélectionné par l'utilisateur dans mon test unitaire. J'ai réalisé que je ne pouvais pas instancier un objet FileList et File par moi-même, ce qui correspondrait aux objets avec lesquels le navigateur travaille. J'ai donc décidé d'attribuer une fausse FileList à la propriété files de l'entrée et de déclencher manuellement l'événement de modification:

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!');
    });

Malheureusement, cela provoque l'erreur suivante dans le contrôleur qui montre que cette tentative a échoué car les fichiers n'ont pas été affectés du tout à l'élément d'entrée:

TypeError: 'undefined' n'est pas un objet (évaluation de 'evt.target.files')

J'ai déjà découvert que le input.files la propriété est lecture seule pour des raisons de sécurité. J'ai donc commencé une autre approche en envoyant un changement personnalisé qui fournirait la propriété des fichiers, mais toujours sans succès.

Donc, histoire courte: je serais impatient d'apprendre une solution de travail ou toute meilleure pratique sur la façon d'aborder ce cas de test.

20
Mobiletainment

Repensons AngularJS, DOM must be handled in a directive

Nous ne devons pas traiter l'élément DOM dans un contrôleur, c'est-à-dire element.on('change', .., en particulier à des fins de test. Dans un contrôleur, vous parlez aux données, pas au DOM.

Ainsi, ces onchange devraient être une directive comme la suivante

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

Cependant, malheureusement, ng-change ne fonctionne pas bien avec type="file". Je ne suis pas sûr que la future version fonctionne avec cela ou non. Cependant, nous pouvons toujours appliquer la même méthode.

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

et dans le contrôleur, nous définissons simplement une méthode

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

Maintenant, tout n'est qu'un test de contrôleur normal. Plus besoin de traiter avec angular.element, $compile, triggers, etc.! :)

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

MISE À JOUR: Merci à @PeteBD,

Depuis angularjs version 1.2.22, les jqLite prennent désormais en charge le passage d'un objet d'événement personnalisé à triggerHandler(). Voir: d262378b


Si vous utilisez uniquement jqLite ,

la triggerHandler() ne fonctionnera jamais car elle passera un objet événement factice aux gestionnaires.

L'objet événement factice ressemble à ceci (copié de jqLite.js # L962 )

{
  preventDefault: noop,
  stopPropagation: noop
}

Comme vous pouvez le voir, il n'a même pas de propriété target.

Si vous utilisez jQuery ,

vous pouvez déclencher un événement avec un objet événement personnalisé comme celui-ci:

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

et le evt.target.files sera le fileList comme vous vous y attendez.

J'espère que cela t'aides.

11
runTarm

Voici un exemple de spécification de fichier/image d'entrée utilisant 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