Why vitest seams to not wait the end of async functions? And why an observer can help with that? #6647
-
I have a javascript code to a web application. I am using vite as bundler and vitest browser mode as test library. I have an async function that get the data to render the custom HTML Element. If the data is found in the local storage it get's from there, else it fetches the data using window.fetch("/api/data"). Here my Custom HTML component code: export class MyCustomeHTMLClass extends HTMLElement {
static storageKey = "data";
static apiEndPoint = "api/data";
constructor() {
super();
this.keyData = null;
}
static get observedAttributes() {
return ["data"];
}
// Component Life Cycle functions
connectedCallback() {
this.getDataAvailable();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "data") {
this.keyData = newValue;
const divElement = document.createElement("div");
divElement.innerHTML = `Data key: ${this.keyData.key}`
}
}
// Component Data
async getDataAvailable() {
const storedData = localStorage.getItem(MyCustomeHTMLClass.storageKey);
if (storedData !== null) {
this.setAttribute("data", storedData);
} else {
window.fetch(MyCustomeHTMLClass.apiEndPoint).then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
}).then((data) => {
const dataString = JSON.stringify(data);
localStorage.setItem(MyCustomeHTMLClass.storageKey, dataString);
this.setAttribute("data", dataString);
}).catch(error => {
console.error('Error fetching data:', error);
});
}
}
}
customElements.define('my-custom-class', MyCustomeHTMLClass); I created 2 tests to verify if the componente has the right behavior: import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MyCustomeHTMLClass } from './MyCustomeHTMLClass.js';
describe('Get Data for the MyCustomeHTMLClass', () => {
beforeEach(() => {
window.localStorage.clear();
vi.resetAllMocks();
});
it('Should use data from localStorage if found', async () => {
const mockStoredData = JSON.stringify({ key: "storedValue" });
window.localStorage.setItem(MyCustomeHTMLClass.storageKey, mockStoredData);
const element = document.createElement('my-custom-class');
document.body.appendChild(element);
await element.getDataAvailable();
expect(element.keyData).toEqual(mockStoredData);
expect(element.getAttribute('data')).toEqual(mockStoredData);
document.body.removeChild(element);
});
it('Should fetch data and save in the localStorage if not available', async () => {
const mockFetchedData = { key: "fetchedValue" };
window.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockFetchedData),
})
);
const element = document.createElement('my-custom-class');
document.body.appendChild(element);
await element.getDataAvailable();
const expectedData = JSON.stringify(mockFetchedData);
expect(window.fetch).toHaveBeenCalledWith(MyCustomeHTMLClass.apiEndPoint);
expect(element.getAttribute('data')).toBe(expectedData);
expect(element.keyData ).toEqual(expectedData);
expect(localStorage.getItem(MyCustomeHTMLClass.storageKey)).toBe(expectedData);
document.body.removeChild(element);
});
}); But this does not work and I don't know why. I mocked the window.fetch and the spy is capable to pass the first assertion, so it is calling the function with the right parameters. Debugging I found out that the ".then((data)" function of my code is running after the code runs the expect statements. Reading the docs I tried using await vi.waitFor(() => {
element.getDataAvailable();
}) const myCustomClassElement = await vi.waitFor(() => {
const element = document.createElement("my-custom-class");
document.body.appendChild(element);
return element
}) The only thing that worked was to create an observer to wait the "data" attribute to change: await new Promise((resolve) => {
const observer = new MutationObserver(() => {
if (element.getAttribute('data')) {
observer.disconnect(); // Stop observing once attribute is set
resolve();
}
});
observer.observe(element, { attributes: true });
}); I don't understand why the other function await flag doesn't work and the expect runs before window fetch is completed and the observer's await flag works Am I missing something? Is there something wrong with my solution? dependencies: "@vitest/browser": "^2.0.5",
"@vitest/coverage-istanbul": "^2.0.5",
"playwright": "^1.47.0",
"vite": "^5.3.4",
"vitest": "^2.0.5" vite test config: test: {
exclude: [
...configDefaults.exclude,
'**/public/**',
],
coverage: {
provider: 'istanbul',
reporter: ['html', 'lcov', 'text'],
reportsDirectory: '../build/coverage',
exclude: [
'**/public/**', ...coverageConfigDefaults.exclude
]
},
// inspectBrk: true,
// fileParallelism: false,
browser: {
provider: 'playwright',
enabled: true,
name: 'chromium',
// headless: true,
screenshotDirectory: '../build/screenshots',
screenshotFailures: false
},
}, |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
Haven't looked entirely but // Component Data
async getDataAvailable() {
...
} else {
+ return window.fetch(MyCustomeHTMLClass.apiEndPoint).then((response) => {
- window.fetch(MyCustomeHTMLClass.apiEndPoint).then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
...
}
} which would make |
Beta Was this translation helpful? Give feedback.
Haven't looked entirely but
getDataAvailable
is not returning a promise:which would make
await element.getDataAvailable()
to not await anything, so the behavior you're seeing is a normal javascript behavior.