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;
};
}
现在我正在尝试使用 Mocha
和 Chai
为该方法编写测试。我的想法是,我想检查 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);
}
我实际上会这样做。并为不同的目的创建其中的几个。
我在一个网站上有一个简单的图片上传器和一个 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;
};
}
现在我正在尝试使用 Mocha
和 Chai
为该方法编写测试。我的想法是,我想检查 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);
}
我实际上会这样做。并为不同的目的创建其中的几个。