I have a storybook project where i am using Vite and LitElement components.
To test the components i thought i would use the Vitest library.
But i can't really test my components, it is like if the components aren't initialized / mounted / working (but they work fine in the stories., so i think the problem is with the testing).
I have a breadcrumb component, which dispatches a custom event on the connectedCallback function. On my story i can listen to this event, so i know it is being dispatched.
But i can seem to test it.
What i have:
on the breadcrumb component, inside the connectedCallback function
this.dispatchEvent(new Event('abc-breadcrumb-connected'));
on my breadcrumb.test.ts file:
import type { IWindow } from 'happy-dom';
import { expect, describe, it, beforeEach, vi } from 'vitest';
import '../abc-breadcrumb';
import { AbcBreadcrumb } from "../abc-breadcrumb";
declare global {
interface Window extends IWindow {}
}
describe('Abc breadcrumb', async () => {
it('Dispatches connected event', async () => {
const mockConnectedCallback = vi.fn(() => true);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
mockConnectedCallback()
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await new Promise(resolve => setTimeout(resolve, 0));
expect(mockConnectedCallback).toHaveBeenCalled();
})
});
0n my vite.config.ts i have:
export default defineConfig({
test: {
globals: true,
environment: 'happy-dom',
},
...
})
the error i get:
AssertionError: expected "spy" to be called at least once
I have no idea why it isn't working an would be really happy to get some help.
Thanks!
At first look, using await new Promise(resolve => setTimeout(resolve, 0)); seems like the kind of thing I was unsuccessfully relying on myself to ensure enough ticks had passed for various operations to complete. This often worked in my local browser but failed in CI or resulted in flaky tests.
Why not set up the promise such that the event listener calls resolve() or possibly even mockConnectedCallback(). Then you can be certain the event isn't firing as opposed to only not having been fired when setTimeout resolves.
const mockConnectedCallback = vi.fn(() => true);
let connectedResolve;
const connectedPromise = new Promise(r => connectedResolve = r);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
connectedResolve();
mockConnectedCallback();
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await connectedPromise;
expect(mockConnectedCallback).toHaveBeenCalled();
It makes the expect() a little redundant given that it won't be reached until the awaited promise resolves, but I think awaiting an explicit resolution makes things easier to reason about and doesn't hard-code various assumptions about microtask queues and things into the test code, which more often than not have come back to bite me later.
Related
I'm creating a messaging app and I'm having some trouble with scrolling to the bottom of an ion-content element when a new message is added to an array. I'm using the scrollToBottom() method that comes with ion-content, and I'm using the Composition API in Vue 3.
Consider this snippet:
setup(props) {
const replyContent = ref("")
const messages = ref([])
// References to ion-content in the template
const ionContent = ref(null)
const reply = async () => {
const message = await replyToThread(props.threadId, replyContent.value).then((message) => message)
messages.value.push(message)
nextTick(() => {
console.log("DOM updated!")
if (ionContent.value) {
ionContent.value.$el.scrollToBottom()
}
})
}
return { replyContent, messages, ionContent, reply }
}
replyToThread() performs an API call and returns the new message, and nextTick() should ensure me that the DOM has been updated so that I can have my way with it. The console does successfully log "DOM updated!", but no scrolling to the bottom happens.
But, and somehow this works every time nextTick() doesn't, when I replace the nextTick() code block with the following, it works flawlessly:
setTimeout(() => {
if (ionContent.value) {
ionContent.value.$el.scrollToBottom()
}
}, 200)
I have to set the timeout at around 200 ms, otherwise it doesn't work. But relying on this when something fancy like nextTick() should do the trick feels quite dirty. Does anyone know why this is happening?
That is because nextTick() only guarantees that the actual DOM has been updated: it doesn't mean that the browser has actually finished the layout of the page. That is the reason why you need an arbitrary timeout to ensure the scrolling works, because after 200ms the browser is likely to be done laying things out based on the updated DOM.
To fix this you will probably need to rely on window.requestAnimationFrame:
nextTick(() => {
window.requestAnimationFrame(() => {
if (ionContent.value) {
ionContent.value.$el.scrollToBottom()
}
});
});
If this feels like too much nesting for you, you can create a method that returns a promise based on rAF:
methods: {
rAF: function() {
return new Promise(r => window.requestAnimationFrame(r));
}
}
Then it's a matter of ensuring promises returned by both nextTick() and rAF() are resolved before scrolling:
await nextTick();
await this.rAF();
if (ionContent.value) {
ionContent.value.$el.scrollToBottom();
}
I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.
EDIT
Current example,
it('CALLED THE canOpenURL FUNCTION', () => {
const wrapper = mount(<ResourceCardComponent {...mockProps} />);
const canOpenURLSpy = jest.spyOn(Linking, 'canOpenURL');
wrapper.find('TouchableOpacity').simulate('click');
expect(canOpenURLSpy).toHaveBeenCalled();
canOpenURLSpy.mockReset();
canOpenURLSpy.mockRestore();
});
Error
expect(jest.fn()).toHaveBeenCalled() Expected mock function to have
been called.
Problem
I am using Jest & Enzyme to test a class made with React Native. This class has a function inside of it that when fired off uses the Linking library to call canOpenUrl and openUrl. I can simulate the click event on the mounted component but I am having trouble knowing how much of this I can actually test.
My goal is to check if Linking.canOpenUrl ever fires off.
Exmaple
The function inside the component looks like this,
onPressLink() {
console.log('HEY THIS FUNCTION FIRED WOOT WOOT');
Linking.canOpenURL(this.props.url).then((supported) => {
if (supported) {
Linking.openURL(this.props.url);
}
});
}
I can simulate this firing off like this,
describe('onPressLink has been called!', () => {
it('It clicks the mock function onPressLink!', (done) => {
const wrapper = mount(<MyComponent {...mockProps} />);
const onPressLink = jest.fn();
const a = new onPressLink();
wrapper.find('TouchableOpacity').first().simulate('click');
expect(onPressLink).toHaveBeenCalled();
done();
});
});
Now that does work, but my goal is to use something like this,
expect(Linking.canOpenUrl).toHaveBeenCalled();
But I keep getting this error,
TypeError: Cannot read property '_isMockFunction' of undefined
Current code that is trying to check if this function is ever fired off. Which is inside the parent function that is clicked with the simulate method,
it('calls canOpenURL', () => {
const wrapper = mount(<MyComponent {...mockProps} />);
const canOpenURL = jest.spyOn(wrapper.instance, 'onPressLink');
wrapper.find('TouchableOpacity').simulate('click');
expect('Linking.canOpenUrl').toHaveBeenCalled();
});
Question
What is the proper way to check to see if Linking.canOpenURL is fired when its parent function is executed?
(Since Jest 19.0.0+)
You can spy on the Linking module methods using jest.spyOn().
(1) Tell jest to spy on the module method:
const spy = jest.spyOn(Linking, 'canOpenURL');
(2) After doing everything you need to test it, check the spy:
expect(spy).toHaveBeenCalled();
(3) Clean up and stop spying on the module method
spy.mockReset();
spy.mockRestore();
If you don't want the tests to use the actual implementation of the methods, you can fake them like this:
jest.spyOn(Linking, 'canOpenURL').mockImplementation(() => Promise.resolve());
Where the function passed to mockImplementation will be whatever you want the method to do when called.
Ref https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
When using the actual implementation of your module method, which is asynchronous, the promise might not have been resolved by the time you tested it. You need to make sure any promise is resolved in your method implementation before making any assertions on it.
One way to deal with this is using async/await, like so:
it('...', async () => {
// Wait for promise to resolve before moving on
await wrapper.instance().onPressLink();
// make your assertions
expect(...);
});
Another option is using expect().resolves, available since Jest 20.0.0, where you wait for some promise in the argument to expect() to resolve with a value before making an assertion on that value.
expect(somePromiseThatEventuallyResolvesWithValue).resolves.toBe(Value);
I've done in simplest way:
Steps to spy:
Make spy object for original function using jest
Call original function with / without argument(s)
Assert the function which should be called with valid argument(s)
Reset mock
Restore mock
Here is the sample example
DefaultBrowser.ts which is actual class.
import { Linking } from 'react-native';
export const openDefaultBrowser = async url => {
if (await Linking.canOpenURL(url)) {
Linking.openURL(url);
}
};
DefaultBrowser.test.ts which is test case class.
import { openDefaultBrowser } from '../DefaultBrowser';
import { Linking } from 'react-native';
describe('openDefaultBrowser with validate and open url', () => {
it('validate url', async () => {
const spy = jest.spyOn(Linking, 'canOpenURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
it('open url', async () => {
const spy = jest.spyOn(Linking, 'openURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
});
Hope this helps you.
it('open url', async () => {
jest.spyOn(Linking, 'canOpenURL')
const spy = jest.spyOn(Linking, 'openURL')
openURL(sitePath)
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(sitePath)
})
spy.mockReset()
spy.mockRestore()
})
I'm trying to test what happens in my app on two consecutive ticks. This is what I have so far (the tests fail in the karma devtools, but fail in the command line):
import { mount } from 'avoriaz';
import MyComponent from './MyComponent';
describe('testing', function() {
it('should do something', (done) => {
const wrapper = mount(MyComponent, { store });
wrapper.vm.changeData();
Vue.nextTick(() => {
expect(wrapper.vm.something).to.eql(somethingElse);
wrapper.vm.changeData();
Vue.nextTick(() => {
expect(wrapper.vm.something2).to.eql(somethingElse2);
done();
});
done();
});
});
});
I also tried using then() and catch(), but karma still thinks my failing tests are passing.
Should I only have one done() call? I'm not really sure what this callback is doing.
As indicated here, there is an even better solution, which prevents Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.:
it('should do something', (done) => {
const wrapper = mount(MyComponent, { store });
wrapper.vm.changeData();
Vue.nextTick(() => {
expect(wrapper.vm.something).to.eql(somethingElse);
wrapper.vm.changeData();
Vue.nextTick().then(() => {
expect(wrapper.vm.something2).to.eql(somethingElse2);
}).then(done, done);
});
});
I'd like to also use the async/await version, but I wasn't able to get it to work.
I have a webcomponent that creates a shadow DOM and adds some html to its shadowRoot.
class SomeThing extends HTMLElement {
attachedCallback () {
this.el = this.createShadowRoot();
this.render();
}
render () {
this.el.innerHTML = '<h1>Hello</h1>';
}
}
export default SomeThing;
And I am compiling it with the help of webpack and its babel-core and babel-preset-es2015 plugins.
Also I am using Karma and Jasmine to write my Unit Test. This is what it looks like.
describe('some-thing', function () {
var someElement;
beforeEach(function () {
someElement = document.createElement('some-thing');
});
it('created element should match string representation', function () {
var expectedEl = '<some-thing></some-thing>';
var wrapper = document.createElement('div');
wrapper.appendChild(someElement);
expect(wrapper.innerHTML).toBe(expectedEl);
});
it('created element should have shadow root', function () {
var wrapper = document.createElement('div');
wrapper.appendChild(someElement);
expect(wrapper.querySelector('some-thing').shadowRoot).not.toBeNull();
})
});
I want to see if there is something in the shadowRoot of my element, and want to write test cases for the HTML and events created inside the shadowRoot. But the second test is failing. It is not able to add shadowRoot to the some-element DOM.
If anyone can help me out, that would be helpful.
I am also uploading the full test working project on Github. You can access it via this link https://github.com/prateekjadhwani/unit-tests-for-shadow-dom-webcomponents
Thanks in advance
I had a similar problem testing a web component but in my case I am using lit-element from polymer/lit-element. Lit-element provides life cycle hooks, template rendering using lit-html library (documentation).
So this is my problem and how I solved. I noticed that the component was added and the class executed constructor and I had access to public methods using:
const element = document.querySelector('my-component-name')
element.METHOD();
element.VARIABLE
But it never reached the hook firstUpdated, so I thought the problem was the speed the test executes vs the speed component is created. So I used the promised provided by lit-element API (updateComplete):
Note: I use mocha/chai instead of Jasmine
class MyComponent extends LitElement {
render() {
return html`<h1>Hello</h1>`
}
}
customElements.define('my-component', TodoApp);
let element;
describe('main', () => {
beforeEach(() => {
element = document.createElement("my-component");
document.querySelector('body').appendChild(element);
});
describe('test', () => {
it('Checks that header tag was added to shadowRoot', (done) => {
(async () => {
const res = await element.updateComplete;
const header = element.shadowRoot.querySelector('h1');
assert.notEqual(header, null);
done();
})();
});
});
});
So, my advice is create a promise and resolve it when the render function is executed, use the promise to sync the creation of the component with tests.
I am using this repository to test concepts
https://github.com/correju/polymer-playground