I have the following HTML:
<div class='t-wrapper' id='t-wrapper'> <template id="type1"> <div class='t-container t-container__type1'> <a> <div class='poster'></div> <div class='content'> <div class='title'></div> <div class='description'></div> </div> </a> </div> </template> </div>
And I have the following plain JS file script.js
:
const SETTINGS = { // some settings } class Manager { constructor(params) { this.PUBLISHER_ID = params.PUBLISHER_ID // ...setting other class properties this.init(this.SELECTOR, this.TYPE) } // private methods #generateElement(obj, el) { const element = document.createElement(obj.elType || 'div') // setting attributes and generating children elements return element } #fillData(data, el) { el.querySelector('a').setAttribute('href', data.url) el.querySelector('a').setAttribute('target', SETTINGS[data.origin].target) // ...setting attributes and text content return el } #render(data, type) { if ('content' in document.createElement('template')) { // if HTML template is supported by browser const template = document.querySelector(`#${type}`) let clone = template.content.cloneNode(true) clone = this.#fillData(data, clone) return clone } else { // If HTML template not supported, opt to generating elements let element = this.#generateElement(SETTINGS.types[type].structure) element.classList.add(`t-container__${type}`) element = this.#fillData(data, element) return element } } // end private methods get requestUrl() { return 'constructed URL' } async getData() { const data = // fetching data with fetch() return data } init(elementId, type) { this.getData().then( function (data) { if (data.list && data.list.length) { const fragment = new DocumentFragment() const wrapper = document.querySelector(`#${elementId}`) for (const item of data.list) { const el = this.#render(item, type) fragment.appendChild(el) } wrapper.appendChild(fragment) } }.bind(this) ) } } // Defining all neccessary constants const PUBLISHER_ID = 'some-id' const API_KEY = 'some-key' const SOURCE_ID = '123456789' const COUNT = 6 const SELECTOR = 't-wrapper' new Manager({ PUBLISHER_ID, API_KEY, SOURCE_ID, COUNT, SELECTOR }) module.exports = { Manager }
Basically, once the class Manager
is instantiated, it calls the init()
method. This method is fetching data from an API, and once fetched, it generates HTML elements for each element of the received data.list
array. When an element is being generated, it first checks if a browser supports the HTML template. If it does – the template is being cloned and attributes are being set. If not – the elements are being created with document.createElement()
. The generated <a>
element will have a "target"
attribute which depends on one of the settings – either "_self"
or "_blank"
Everything work and the elements are being generated either way. However, now I need to test with Jest that they are in fact being generated, and when clicking on a link it should be opened either in a new window/tab, or in the same window, depending on the setting.
I’m very, very new to Jest and to testing. After some searching I tried to follow this example. So I created this test:
const { Manager } = require('./script') const PUBLISHER_ID = 'some-id' const API_KEY = 'some-key' const SOURCE_ID = '123456789' const COUNT = 6 describe('Manager Script', () => { let createElement let querySelector let createObjectURL beforeAll(() => { createElement = document.createElement querySelector = document.querySelector createObjectURL = window.URL.createObjectURL }) afterAll(() => { jest.restoreAllMocks() document.createElement = createElement document.querySelector = querySelector window.URL.createObjectURL = createObjectURL }) // ...other tests it('should render elements', () => { const divEl = { setAttribute: jest.fn(), innerHTML: '' } const linkEl = { setAttribute: jest.fn(), innerHTML: '' } const container = { appendChild: jest.fn() } document.createElement = jest.fn().mockImplementation(tagName => { switch (tagName) { case 'div': return divEl case 'a': return linkEl } }) document.querySelector = jest.fn().mockRejectedValueOnce(container) const manager = new Manager({ PUBLISHER_ID, API_KEY, SOURCE_ID, COUNT, }) expect(document.createElement).toBeCalledTimes(1) }) })
But this test fails with Expected number of calls: 1; Received number of calls: 0
I tried to call manager.init()
after the instantiation, tried to set the template support checking to false and go directly to generating with document.createElement()
part, tried to use beforeEach/afterEach
(as in example)… The test keeps failing.
What am I doing wrong and how can I make it work? How can I test all those things?
Advertisement
Answer
In general perception, I think you should use a mocked data instead of getting data from external API.
- It will make your test much more faster.
- It will make your text much more stable.
- It will focus on what the test is meant to test. (The API / Internet Connection are not relevant for testing the creation of the template)
I believe you test will work fine if it will not have to “wait” for the async response.