【问题标题】:TypeError: Attempted to wrap undefined property text as functionTypeError:试图将未定义的属性文本包装为函数
【发布时间】:2022-01-27 05:34:13
【问题描述】:

我有一个导入 jsPDF library 的 javascript 项目

pdf.js 的内容:

import { jsPDF } from "jspdf";

createPDF (transcriptString) {
  const transcriptPdfDoc = new jsPDF()

  transcriptPdfDoc.text(transcriptString, 10, 10)
  transcriptPdfDoc.save('test.pdf')
}

pdf.spec.js 的内容:

import { jsPDF } from "jspdf";

beforeEach(() => {
  sandbox = sinon.sandbox.create()
})

afterEach(() => {
  sandbox.restore()
})
    
describe('createPDF', () => {
  it('should save transcript PDF', () => {
    const transcriptString = 'test transcript string'
    const jsPdfStub = sandbox.spy(jsPDF.prototype, 'text')
   
    //This function is working OK here in karma 
    pdf.createPDF(transcriptString)
    
    expect(jsPdfStub).to.have.been.called
  })
})

上面的单元测试代码给了我这个错误:TypeError: Attempted to wrap undefined property text as function.

【问题讨论】:

  • 如果我的回答有帮助,我不介意点赞或检查接受按钮。
  • 添加了两个不同方法的完整示例来测试这一点。

标签: javascript unit-testing sinon jspdf sinon-chai


【解决方案1】:

这很简单:该原型不包含名为text 的函数,因此会出现错误。我写了这个快速脚本来验证:

import { jsPDF } from "jspdf";

console.dir(jsPDF);
console.log("prototype", jsPDF.prototype);
console.log("prototype has text?", typeof jsPDF.prototype.text !== "undefined");

const pdf = new jsPDF();
console.log(pdf.text);

哪个输出这个

$ node test.js 
[Function: I] {
  API: {
    events: [
      [Array], [Array],
....
....
    RadioButton: [Function: ft],
    CheckBox: [Function: pt],
    TextField: [Function: gt],
    PasswordField: [Function: mt],
    Appearance: {
      CheckBox: [Object],
      RadioButton: [Object],
      createDefaultAppearanceStream: [Function: createDefaultAppearanceStream],
      internal: [Object]
    }
  },
  getPageSize: [Function (anonymous)],
  __bidiEngine__: [Function (anonymous)]
}
prototype { __bidiEngine__: [Function (anonymous)] }
prototype has text? false
[Function (anonymous)]

看了一下the sourcejsPdf的原型基本上没有添加任何东西。当您通过调用构造函数创建新对象时,所有插件和 API 方法都会添加/绑定到 实例

监视text 方法

您无需尝试在原型上查找方法,而只需监视实例方法。

const pdf = new jsPDF();
const spy = sinon.spy(pdf, "text");

const transcriptString = "test transcript string";
pdf.text(transcriptString, 10, 10);

console.log("called?", spy.called); // ==> true

在您使用自己的 createPDF() 函数的示例中,这将不起作用,因为您在闭包中屏蔽了实例的访问。您需要某种方式连接到实例创建过程。有几种方法可以“解决”这个问题。一种方法是提取一个工厂函数来创建jsPDF 实例和一个相关的setter,让您替换被调用的构造函数。另一种方法是在测试中使用"link seam" 来替换jspdf 模块。

使用链接接缝(模块更换)

看起来像这样:

import {jsPDF} from 'jspdf';

// later ... in your test
let createdInstance;
let spy;
const createPdf = proxyquire("../src/createPdf", {
      jspdf: {
        jsPDF: (...args) => {
            createdInstance = new jsPDF(...args);
            sinon.spy(createdInstance, 'text');
            return createdInstance;     
        });
      }
});

pdf.createPDF(transcriptString)
    
expect(spy).to.have.been.called

这具有不需要修改或重构原始代码的优点,但它是非常特定于环境的。 proxyquire 只能在 Node 中工作,你需要为 Webpack 提供其他东西,为 Vite、Jest 等提供不同的解决方案。

“手动”DI

如果你稍微重构一下代码,无论你使用什么环境或框架,它都会更容易测试:

export class PdfCreator(){
    constructor(opts) {
        this.jsPDF = opts?.stubs?.jsPDF || jsPDF;
    }

    createPdf(text){
        const transcriptPdfDoc = new this.jsPDF()
        transcriptPdfDoc.text(transcriptString, 10, 10)
        transcriptPdfDoc.save('test.pdf')
    }
}

const defaultInstance = new PdfCreator();
export default createPdf = (text) => defaultInstance.createPdf();

通过这种方法,您可以完全控制创建过程。如果你想测试pdf创建,你可以创建一个creator = new PdfCreator({stubs: {jsPDF:spy}});并调用creator.createPdf('foo')来测试它。

【讨论】:

    猜你喜欢
    • 2017-07-05
    • 2020-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-11
    • 1970-01-01
    相关资源
    最近更新 更多