Timeout simulation not working with testing-library and useFakeTimers - vue.js

I'm working on a vueJS component that allows to display a modal after 5 seconds. the component works well as expected.
<template>
<vue-modal v-if="showModal" data-testid="modal-testid" />
</template>
<script>
export default {
name: "TimeoutExample",
data() {
return {
showModal: false,
}
},
mounted() {
setTimeout(() => this.displayModal(), 5000)
},
methods: {
displayModal: function() {
this.showModal = true;
}
}
};
</script>
I implemented the unit tests using jest, testing-library and I wanted to use jest.useFakeTimers to simulate the timeout, but the test is KO.
// testing file
describe.only('Vue Component (mobile) 2', () => {
beforeAll(() => {
isMobile.mockImplementation(() => true)
})
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
})
it('should render title after `props.delay` milliseconds', () => {
const { queryByTestId } = myRender({
localVue: myMakeLocalVue(),
})
jest.advanceTimersByTime(5001)
expect(queryByTestId('modal-testid')).toBeVisible()
})
})
do you have any idea how i can test this behavior?

remove this jest.spyOn(global, 'setTimeout'). jest will do it's own magic with for this with useFakeTimers
I suppose you can not use async and done callback in one test case. Which version of jest do you use?
Add await localVue.$nextTick() after advanceTimersByTime to wait until Vue apply all the changes

It works for me after calling advanceTimersByTime inside waitFor.
describe.only('Vue Component (mobile) 2', () => {
beforeAll(() => {
isMobile.mockImplementation(() => true)
})
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
})
it('should render title after `props.delay` milliseconds', async () => {
const { queryByTestId } = myRender({
localVue: myMakeLocalVue(),
})
await waitFor(() => {
jest.advanceTimersByTime(5001)
})
expect(queryByTestId('modal-testid')).toBeVisible()
})
})

Related

Vue unit test is holds old component code in wrapper

I am doing unit test of vue component methods through vue-test-utils and facing an weird issue. wrapper.vm.somemthod() is executing old code that was written earlier inside the method. It's printing old console.log statements. If I put new console.log statement, it's not printing that at all. Am I missing something?
import { mount, shallowMount } from '#vue/test-utils'
import TestComponent from '#/components/TestComponent.vue'
const mockMixin = {
methods: {
InnerMethod() {
return 2;
},
}
}
describe('Test Screen', () => {
let wrapper;
let mock;
beforeAll(() => {
mock = new MockAdapter(api);
})
beforeEach(() => {
jest.resetModules();
wrapper = mount(TestComponent, {
i18n,
vuetify,
mixins: [mockMixin],
data() {
},
mocks: {
$t: (msg) => msg,
$config: (value) => value,
$store: (value) => value,
$route: (value) => value,
}
});
})
afterEach(() => {
mock.reset();
wrapper.destroy();
wrapper = null;
});
describe('Component Test', () => {
it('getdata', async () => {
expect(wrapper.vm.somedata.length).toBe(0);
const spy = jest.spyOn(wrapper.vm, 'InnerMethod');
wrapper.vm.someMethod();
expect(spy).toBeCalledTimes(1);
expect(wrapper.vm.somedata.length).toBe(2);
});
});
});

Jest: How I should change the mock data of Vuex in each test?

I've been working in a test where I need the data from Vuex. However, I'm having some problems, I need to change that data in each test in order to test the functionality of the component.
Here is my component:
<template>
<div id="cb-items-displayer" #click="textClick">
<span>(</span>
<p>{{ text }}</p>
<span>)</span>
</div>
</template>
<script lang="ts" setup>
import { capitalize } from '#/utils/capitalize'
import { ItemsDisplayer } from '#/models/ItemsDisplayer'
import { computed, PropType } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const props = defineProps({
type: {
type: String,
default: '',
},
menuType: {
type: String,
default: '',
},
items: {
type: Array as PropType<ItemsDisplayer[]>,
default: () => [],
}
})
const emit = defineEmits<{
(event: 'textClicked'): void
}>()
const text = computed(() => {
const param = props.menuType === 'radio' ? 'One' : 'Many'
console.log( "TYPEEE ", props.type, " ", param )
const itemsIds = store.getters['filters/get' + capitalize(props.type) + param]
console.log("ITEMSSS", JSON.stringify(itemsIds))
return getTextToShow(itemsIds)
})
const getTextToShow = (itemsIds: string) => {
//TODO - improve it
if (itemsIds === 'all') {
return 'all'
} else if (itemsIds.length === 0) {
return '-'
} else if (itemsIds.length === 1) {
return getName(itemsIds[0], props.items)
} else {
return itemsIds.length
}
}
const textClick = () => {
emit('textClicked')
}
const getName = (id: string, items: ItemsDisplayer[]) => {
const found: ItemsDisplayer = items.find((x) => x.id! === id) as ItemsDisplayer
console.log("GETNAME ", found.name)
return found?.name
}
</script>
And this is the test:
import { render, screen, click, waitFor } from '#tests/app-test-utils'
import ItemsDisplayer from './ItemsDisplayer.vue'
import { capitalize } from '#/utils/capitalize'
let mockStoreCommit: jest.Mock
jest.mock('vuex', () => ({
...jest.requireActual('vuex'),
useStore: () => ({
getters: {
[`filters/get${capitalize('categories')}Many`]: [],
},
commit: mockStoreCommit,
}),
}))
describe('ItemsDisplayer', () => {
beforeEach(() => {
mockStoreCommit = jest.fn()
render(
ItemsDisplayer,
{},
{
props: {
type: 'categories',
menuType: 'checkbox',
items: [
{
box_templates:"",
id:"1",
name:"Culture"
},
{
box_templates:"",
id:"2",
name:"Economy"
},
{
box_templates:"",
id:"3",
name:"Education"
}
]},
}
)
})
it('renders the component', async() => {
await screen.getByText('-')
})
it('renders the component with one item', async() => {
//DON'T WORK HERE THERE SHOULD BE A CHANGE OF DATA IN THE MOCKED STORE IN ORDER TO WORK
await screen.getByText('Culture')
})
})
My problem is that I need to change the value of [filters/get${capitalize('categories')}Many] to ["1"] in the second test.
I tried several things in order to change the mocked data but they don't work. How can I change the mocked store data in each test?
Thanks!
You can achieve this by lazy loading your vue component:
Add jest.resetModules(); in the beforeEach to reset all of the imported modules before each test so they can be re-evaluated and re-mocked:
beforeEach(() => {
jest.resetModules();
In each unit test, you will first need to import the vue component using the require syntax as follows:
const ItemsDisplayer = require('./ItemsDisplayer.vue').default;
Then add the mock directly after the import with the [`filters/get${capitalize('categories')}Many`] value being set to whatever you want it to be:
jest.mock('vuex', () => ({
...jest.requireActual('vuex'),
useStore: () => ({
getters: {
[`filters/get${capitalize('categories')}Many`]: ["1"],
},
commit: mockStoreCommit,
}),
}));
I have noticed that you do your rendering in the beforeEach. Unfortunately because you import and mock your modules during the test, the rendering will need to be done after these have taken place - hence you will need to either move that logic into your unit test or extract it into another function which can be called from within the unit test.
Each unit test should look something like this:
it('renders the component', async() => {
const ItemsDisplayer = require('./ItemsDisplayer.vue').default;
jest.mock('vuex', () => ({
...jest.requireActual('vuex'),
useStore: () => ({
getters: {
[`filters/get${capitalize('categories')}Many`]: ["1"],
},
commit: mockStoreCommit,
}),
}));
// beforeEach logic here or a call to a function that contains it
await screen.getByText('-')
})

Unit tests, check if function have been called

I need to implement a test that checks if the function has been called on the button click
onSave (e) {
this.$qiwaApi.createDimension()
.then(() => {})
.catch(err => this.showSnackbar(err.message))}
I need to test the function createDimension. In my test i mocked it
const createComponent = () => {
wrapper = mount(dimensions, {
store,
localVue,
mocks: {
$qiwaApi: {
createDimension: function (e) {
return new Promise((resolve) => { resolve({}) })
}
}
},
router
})
}
In the project, the function exported this way
export default $axios => ({
createDimension (data, surveyId) {
return $axios.post(`/lmi-admin/surveys/${surveyId}/dimension`, {
data: {
attributes: {
...data
}
}
})
}
})
I expect this test to work. But for some reason wrapper.qiwaApi or wrapper.createDimension return undefined
expect(wrapper.$qiwaApi.createDimension).toHaveBeenCalled()
The wrapper doesn't provide access to your mocks that way.
You would have to hold a reference to a jest.fn(), and then verify the calls on that reference directly instead of trying to pull it out of the wrapper:
it('calls createDimension on button click', async () => {
const createDimension = jest.fn(() => Promise.resolve())
const wrapper = mount(dimensions, {
mocks: {
$qiwaApi: {
createDimension
}
}
})
await wrapper.find('[data-testid="save"]').trigger('click')
expect(createDimension).toHaveBeenCalled()
})
demo

Vue.js/nuxt.js - test a dynamically added method inside a component

I'm trying to make a test for dynamically created methods in one of my components the code goes like this.
<template>
<div id="app">
<div #click="executeDynamic('myCustomFunction')">Click me!</div>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
// These contain all dynamic user functions
userFuncs: {}
}
},
created () {
window.setTimeout(() => {
this.$set(this.userFuncs, 'myCustomFunction', () => {
console.log('whoohoo, it was added dynamically')
})
}, 2000)
},
methods: {
executeDynamic (name) {
if (this.userFuncs[name]) {
this.userFuncs[name]()
} else {
console.warn(`${name} was not yet defined!`)
}
}
}
};
</script>
test file
import WorkDateTime from "#/components/WorkDateTime.vue"
import Vue from "vue"
describe("WorkDateTime.vue", () => {
it("allowedDatesFrom: today -> NG", () => {
const that = {
$set: Vue.set
}
expect(WorkDateTime.data.userFuncs['myCustomFunction']).toBeTruthy()
})
}
code pen
https://codesandbox.io/s/vue-template-forked-ec7tg?file=/src/App.vue:0-662
Try something like that:
import { shallowMount } from '#vue/test-utils';
import WorkDateTime from '#/components/WorkDateTime.vue';
describe('WorkDateTime.vue', () => {
it('userFuncs empty', () => {
let wrapper = shallowMount(WorkDateTime);
expect(wrapper.vm.userFuncs).toStrictEqual({});
});
it('userFuncs filled', async () => {
let wrapper = shallowMount(WorkDateTime);
let wait3Seconds = () => new Promise(resolve => setTimeout(() => resolve(), 3000));
await wait3Seconds();
expect(wrapper.vm.userFuncs['myCustomFunction']).toBeInstanceOf(Function);
});
});

how to do page navigation (routing) using karma-Jasmine test

I am writing the angular (Karma-Jasmine) test cases and I want to navigate between the pages. How to navigate between pages using karma-Jasmine.
1) Test a component in which navigation is used: navigate method should be called when you do an action (assertion like toHaveBeenCalled OR toHaveBeenCalledWith)
it('should redirect the user to the users page after saving', () => {
let router = TestBed.get(Router);
let spy = spyOn(router, 'navigate');
component.save();
expect(spy).toHaveBeenCalledWith(['users'])
});
2) Also test your routes that proper component will be used
app.routes.spec.ts
import { routes } from './app.routes'
it('should contain a route for users', () => {
expect(routes).toContain({path: 'users', component: UserComponent})
});
3) You can use useValue for testing different activatedRouteParams (just configure then and pass to providers, see example).
Component ts file example:
ngOnInit() {
this.contextType = this.route.snapshot.paramMap.get('contextType');
this.contextId = this.route.snapshot.paramMap.get('contextId');
this.contextFriendlyId = this.route.snapshot.paramMap.get('contextFriendlyId');
}
Spec file (configureTestData is a function that allows you to pass different configurable values, in my case activatedRouteParams)
export function configureTestData(activatedRouteParams) {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SetUpComponent],
imports: [RouterTestingModule],
providers: [
{
provide: ActivatedRoute, useValue: activatedRouteParams
}
]
});
}));
}
describe('SetUp Component:Folder ', () => {
let component: SetUpComponent;
let fixture: ComponentFixture<SetUpComponent>;
configureTestData({
snapshot: {
paramMap: convertToParamMap({
contextType: 'Folder',
contextId: 'FX6C3F2EDE-DB25-BC3D-0F16-D984753C9D2E',
contextFriendlyId: 'FX17373'
})
}
});
beforeEach(() => {
fixture = TestBed.createComponent(SetUpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create set up component for folder', () => {
expect(component).toBeTruthy();
});
it('should create alert with required properties', () => {
expect(component.contextType).toEqual('Folder);
.... etc
});
});
4) router-outlet and routerLink
Template file:
<nav>
<a routerLink="todos"></a>
</nav>
<router-outlet></router-outlet>
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([])],
declarations: [AppComponent]
});
});
it('should have router outlet', () => {
let de = fixture.debugElement.query(By.directive(RouterOutlet));
expect(de).not.toBeNull();
});
it('should have a link to todos page', () => {
let debugElements = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
let index = debugElements.findIndex(de => de.properties['href'] === '/todos');
expect(index).toBeGreaterThan(-1);
});
5) Stub for ActivatedRoute where we can push params
component.ts
ngOnInit() {
this.route.params.subscribe(p => {
if (p['id'] === 0) {
this.router.navigate(['not-found']);
}
});
}
Spec file:
class RouterStub {
navigate(params) {}
}
class ActivatedRouteStub {
private subject = new Subject();
get params () {
return this.subject.asObservable();
}
push(value) {
this.subject.next(value);
}
}
describe('Navigation Testing', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [
{provide: Router, useClass: RouterStub},
{provide: ActivatedRoute, useClass: ActivatedRouteStub}
]
});
});
it('should navigate to invalid page when invalid params passed', () => {
let router = TestBed.get(Router);
let spy = spyOn(router, 'navigate');
let route: ActivatedRouteStub = TestBed.get(ActivatedRoute);
route.push({id: 0});
expect(spy).toHaveBeenCalledWith(['not-found'])
});
});