Mock img Element in Jest
I recently started implementing some tests for one of my open source libraries. the library is providing some high level APIs to work with HTML Canvas. inserting an image into the canvas is one of the features. but here let’s simplify the test by removing canvas parts. so this is the unit test:
test('it should load the image', () => {
const img = document.createElement('img')
img.src = '<img url>' waitForImage(img).then(loadedImage => {
except(loadedImage.naturalWidth).toBe(1200) })
})function waitForImage(img) {
return new Promise(resolve => {
img.onload = () => resolve(img)
})
}
but the test will fail because of timeout issue when you run the test suite
● Rotation › it should load the image: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
From the error’s description, it seems Jest is trying to load the image but a timeout is happening.
in order to fix the issue we need to mock the document.createElement
function to bypass the timeout
first add the setupFile.js
or setupFile.ts
into your jest configuration
{
setupFiles: ['/path/to/jest/config/setupFile.js']}
and then let’s override the createElement
function
global.document.createElement = (function (create) { return function () {
const element: HTMLElement = create.apply(this, arguments)
if (element.tagName === 'IMG') {
setTimeout(() => {
element.onload(new Event('load'))
}, 100)
} return element
}
})(document.createElement)
the above snippet mocking img
element and calls onload
function with a 100ms delay
we can even make it better. let’s say we need to test different size of images. so I change my test suite to:
test('it should load the image', () => {const img = document.createElement('img')
img.src = '1200x800' waitForImage(img).then(loadedImage => {
except(loadedImage.naturalWidth).toBe(1200)
})
})function waitForImage(img) {
return new Promise(resolve => {
img.onload = () => resolve(img)
})
}
and change setupFile
global.document.createElement = (function (create) {return function () {
const element: HTMLElement = create.apply(this, arguments)
if (element.tagName === 'IMG') {
setTimeout(() => {
const [w, h] = element.getAttribute('src').split('x')
element.setAttribute('width', w)
element.setAttribute('naturalWidth', w)
element.setAttribute('height', h)
element.setAttribute('naturalHeight', h) element.onload(new Event('load'))
}, 100)
} return element
}
})(document.createElement)
the change fakes the image’s width and height based on the given src
in test suite.
in the same way you can add more logics to override function based on your needs
enjoy testing!