How to make unit test "location.href" by jest Vue.js - vue.js

What I want to do
I wanna make test that is redirect by click.
if possible, I wanna make test not only method called assertion but redirect "URL" assertion.
Test Target Code
<p class="target" v-on:click="redirect">Click!</p>
<script>
export default {
methods: {
redirect() {
window.location.href = '/home'
},
}
}
Test Code
import { shallowMount } from '#vue/test-utils'
import redirectComponent from '../components/RedirectComponent.vue'
describe('Redirect component', () => {
const wrapper = shallowMount(redirectComponent)
it('Redirect By Click Test', () => {
wrapper.find('.target').trigger('click');
expect(window.location.href).toEqual('/home');
})
})
result
Expected: "/home"
Received: "http://localhost/"
How can I make redirect test?

window.location.href gives you the URL of the current page;
window.location.pathname will give you the path name of the current page, which I believe, is what you are looking for.

This is my Solution!
import { shallowMount } from '#vue/test-utils'
import redirectComponent from '../components/RedirectComponent.vue'
describe('Redirect component', () => {
const wrapper = shallowMount(redirectComponent)
Object.defineProperty(window, 'location', {
value: {
href: 'http://localhost',
},
configurable: true,
});
it('Redirect By Click Test', () => {
wrapper.find('.target').trigger('click');
expect(window.location.href).toEqual('/home');
})
})

Related

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('-')
})

Vuex + Jest + Composition API: How to check if an action has been called

I am working on a project built on Vue3 and composition API and writing test cases.
The component I want to test is like below.
Home.vue
<template>
<div>
<Child #onChangeValue="onChangeValue" />
</div>
</template>
<script lang="ts>
...
const onChangeValue = (value: string) => {
store.dispatch("changeValueAction", {
value: value,
});
};
</scirpt>
Now I want to test if changeValueAction has been called.
Home.spec.ts
...
import { key, store } from '#/store';
describe("Test Home component", () => {
const wrapper = mount(Home, {
global: {
plugins: [[store, key]],
},
});
it("Test onChangeValue", () => {
const child = wrapper.findComponent(Child);
child.vm.$emit("onChangeValue", "Hello, world");
// I want to check changeValueAction has been called.
expect(wrapper.vm.store.state.moduleA.value).toBe("Hello, world");
});
});
I can confirm the state has actually been updated successfully in the test case above but I am wondering how I can mock action and check if it has been called.
How can I do it?
I have sort of a similar setup.
I don't want to test the actual store just that the method within the component is calling dispatch with a certain value.
This is what I've done.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
Hope this will help you further :)

Component clean up fails between tests with Vue test-utils

I have a simple component (HelloComponent) and a couple of tests. First test shallow mounts the component, prints it (wrapper of the component) on the console, and finally calls destroy() api on it. And the second test just prints it without mounting it. I was expecting the second test to print undefined but it prints the same thing (full component markup) as first test. Is my expectation incorrect ?
<!-- HelloComponent.vue -->
<template>
<div>
Hello {{name}}!
</div>
</template>
<script lang="ts">
export default {
name: 'Hello',
data() {
return {
name: ''
};
},
methods: {
setName(name) {
this.name = name;
}
}
}
</script>
import { shallowMount } from '#vue/test-utils';
import HelloComponent from '#/HelloComponent.vue';
describe('Hello component unit tests', () => {
let wrapper;
describe('Set 1', () => {
it('should load component', () => {
wrapper = shallowMount(HelloComponent, {});
expect(wrapper.exists()).toBe(true);
wrapper.vm.setName('oomer');
console.log(wrapper.html());
wrapper.destroy();
});
});
describe('Set 2', () => {
it('should log empty component', () => {
expect(wrapper.vm.name).toEqual('oomer');
console.log(wrapper.html());
});
});
});

Unable to test a lodash debounced vue method

I've been trying to test a confirmation button using jest and vue-test-utils. I'm using a debounced method to handle accidental double-clicking.
I've tried using jest.useFakeTimers() and jest.runOnlyPendingTimers(), but I'm still not seeing my button text changing in my tests. I've created an isolated test file below - any advice on what I'm doing wrong here would be greatly appreciated!
import { shallowMount, createLocalVue } from '#vue/test-utils'
import _ from 'lodash'
jest.useFakeTimers()
const myComponent = {
data(){
return {
confirming: false
}
},
computed: {
state(){
return this.confirming ? 'are you sure?' : 'default'
}
},
template: `<div #click="changeState">{{ state }}</div>`,
methods: {
changeState(){
this.doConfirm()
},
doConfirm: _.debounce(function(){
if(!this.confirming){
this.confirming = true
}else{
this.confirming = false
}
}, 500)
}
}
describe('Testing debounce methods', () => {
let $wrapper
beforeEach(() => {
$wrapper = shallowMount(myComponent)
})
test('Check default state', () => {
expect($wrapper.text()).toContain('default')
})
test('Check state changes with click', () => {
$wrapper.trigger('click')
jest.runOnlyPendingTimers()
expect($wrapper.text()).toContain('are you sure?')
})
})

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'])
});
});