'TypeError: Attempted to wrap undefined property text as function
I have a javascript project that imports jsPDF library
Contents of pdf.js:
import { jsPDF } from "jspdf";
createPDF (transcriptString) {
const transcriptPdfDoc = new jsPDF()
transcriptPdfDoc.text(transcriptString, 10, 10)
transcriptPdfDoc.save('test.pdf')
}
Contents of 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
})
})
The above unit test code give me this error: TypeError: Attempted to wrap undefined property text as function.
Solution 1:[1]
It's quite straight forward: that prototype does not contain a function called text
, hence the error. I wrote this quick script to verify:
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);
Which outputs this
$ 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)]
Had a look at the source and there is basically nothing added to the prototype of jsPdf
. All the plugins and API methods are added/bound to the instance when you create a new object by calling the constructor.
To spy on the text
method
Instead of trying to find the method on the prototype, you just spy on the instance method.
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
In your example of using your own createPDF()
function this will not work as you shield the instance from access in the closure. You need some way of hooking into the instance creation process. There are several ways to "fix" this. One way could be to extract a factory function to create the jsPDF
instance and a related setter that lets you replace the constructor that gets called. Another way could be to use a "link seam" in your test to replace the jspdf
module.
Using a link seam (module replacement)
That would look something like this:
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
This has the advantage of not needed to touch or restructure the original code, but it is very environment specific. proxyquire
will only work in Node and you need something else for Webpack, a different solution for Vite, Jest, etc.
"Manual" DI
If you restructure the code a bit it will make it easier to test regardless of what environment or framework you are using:
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();
With this approach you have full control of the creation process. If you want to test the pdf creation, you can just create a creator = new PdfCreator({stubs: {jsPDF:spy}});
and call creator.createPdf('foo')
to test it.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |