Javascript 使用 mocha 测试 html5 文件 api?

Javascript testing with mocha the html5 file api?

我在一个网站上有一个简单的图片上传器和一个 javascript 函数,它使用 FileReader 并将图片转换为 base64 以显示给用户,而无需将其上传到实际服务器。

function generateThumb(file) {
    var fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.onload = function (e) {
        // Converts the image to base64
        file.dataUrl = e.target.result;
    };
}

现在我正在尝试使用 MochaChai 为该方法编写测试。我的想法是,我想检查 file.dataUrl 是否已成功创建并且它是 base64。所以我想在测试环境中以某种方式模拟本地文件(不知道该怎么做)。或者我根本不应该测试它并假设它有效?

这里的答案有点依赖。除了加载文件内容并将其分配给传入对象的 属性 之外,您的 "generateThumbs" 方法是否还有其他逻辑?或者它是否有其他逻辑,例如从图像数据生成缩略图,读取文件属性并将它们分配给文件对象?等等?

如果是这样,那么我实际上建议您用自己的对象模拟 FileReader 对象,以便您可以控制测试结果。但是,您要测试的不是 FileReader 功能,而是您自己的逻辑。因此,您应该假设 FileReader 可以正常工作并测试依赖于它的代码是否正常工作。

现在由于您发布的方法有点偏小,在这种情况下,我假设它有效,重命名该方法并着手测试您的其余代码。但是有这样一个模拟的地方,我必须承认弄清楚如何模拟事件目标非常有趣,所以我将在这里举一个例子,使用你的方法作为基础:

//This implements the EventTarget interface
//and let's us control when, where and what triggers events
//and what they return
//it takes in a spy and some fake return data

var FakeFileReader = function(spy, fakeData) {
    this.listeners = {};
    this.fakeData = fakeData;
    this.spy = spy;
    this.addEventListener('load', function () {
        this.spy.loaded = true;
    });
};
//Fake version of the method we depend upon
FakeFileReader.prototype.readAsDataURL = function(file){
    this.spy.calledReadAsDataURL = true;
    this.spy.file = file;
    this.result = this.fakeData;
    this.dispatchEvent({type:'load'}); //assume file is loaded, and send event
};
FakeFileReader.prototype.listeners = null;
FakeFileReader.prototype.addEventListener = function(type, callback) {
    if(!(type in this.listeners)) {
        this.listeners[type] = [];
    }
    this.listeners[type].push(callback);
};

FakeFileReader.prototype.removeEventListener = function(type, callback) {
    if(!(type in this.listeners)) {
        return;
    }
    var stack = this.listeners[type];
    for(var i = 0, l = stack.length; i < l; i++) {
        if(stack[i] === callback){
            stack.splice(i, 1);
            return this.removeEventListener(type, callback);
        }
    }
};

FakeFileReader.prototype.dispatchEvent = function(event) {
    if(!(event.type in this.listeners)) {
        return;
    }
    var stack = this.listeners[event.type];
    event.target = this;
    for(var i = 0, l = stack.length; i < l; i++) {
        stack[i].call(this, event);
    }
};

// Your method

function generateThumb(file, reader){
    reader.readAsDataURL(file);
    reader.addEventListener('load', function (e) {
        file.dataUrl = base64(e.target.result);
    });

}

现在我们有一个漂亮的小对象,它的行为类似于 FileReader,但我们控制它的行为,我们现在可以使用我们的方法并将其注入,从而使我们能够使用这个模拟进行测试,并使用真实的东西进行测试生产。

所以我们现在可以编写很好的单元测试来测试它: 我假设你有 mocha 和 chai setup

describe('The generateThumb function', function () {
    var file = { src: 'image.file'};
    var readerSpy = {};
    var testData = 'TESTDATA';
    var reader    = new FakeFileReader(readerSpy, testData);
    it('should call the readAsDataURL function when given a file name and a FileReader', function () {
        generateThumb(file, reader);
        expect(readerSpy.calledReadAsDataURL).to.be.true;
        expect(readerSpy.loaded).to.be.true;
    });
    it('should load the file and convert the data to base64', function () {
        var expectedData = 'VEVTVERBVEE=';
        generateThumb(file, reader);
        expect(readerSpy.file.src).to.equal(file.src);
        expect(readerSpy.file.dataUrl).to.equal(expectedData);
    });
});

这是一个有效的 JSFiddle 示例: https://jsfiddle.net/workingClassHacker/jL4xpwwv/2/

这样做的好处是您可以创建此模拟的多个版本:

  • 给出正确数据后会发生什么?
  • 如果提供的数据不正确会怎样?
  • 当永远不会调用回调时会发生什么?
  • 如果放入的文件太大会怎样?
  • 当输入某种 mime 类型时会发生什么?

如果您只需要一个事件,您当然可以大量简化模拟:

function FakeFileReader(spy, testdata){
    return {
        readAsDataURL:function (file) {
            spy.file = file;
            spy.calledReadAsDataURL = true;
            spy.loaded = true;
            this.target = {result: testdata};
            this.onload(this);
        }
    };
}

function generateThumb(file, reader){
    reader.onload = function (e) {
        file.dataUrl = base64(e.target.result);
    };
    reader.readAsDataURL(file);
}

我实际上会这样做。并为不同的目的创建其中的几个。

简单版: https://jsfiddle.net/workingClassHacker/7g44h9fj/3/