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'])
});
});
Related
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);
});
});
});
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('-')
})
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()
})
})
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');
})
})
I have a TitleComponent that uses ActivedRoute.snapshot.data and Router to update data from app-routing.
This is my app-routing.module
export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{
path: 'home',
component: MainHomeComponent,
data: {
title: 'Home',
}
},
];
#NgModule({
imports: [RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
scrollOffset: [0, 64] // [x, y]
} )],
exports: [RouterModule]
})
export class AppRoutingModule { }
and this is my TitleComponent.ts
export class TitleComponent implements OnInit {
titleTab: string;
subTitle: string;
isTablet$: Subscribable<boolean> = this.breakpointsService.isTablet$;
constructor(private router: Router, private route: ActivatedRoute, private breakpointsService: BreakpointsService) {
this.router.events.subscribe((data) => {
if (data instanceof NavigationEnd) {
console.log('title is= ',this.route.snapshot.data['title']);
// console.log(this.route.root.firstChild.snapshot.data.subTitle);
this.titleTab = this.route.snapshot.data['title'];
if (this.route.snapshot.data['subTitle']) {
this.subTitle = this.route.snapshot.data['subTitle'];
} else {
this.subTitle = '';
}
}
});
}
ngOnInit() {
}
}
Now i want to test my TitleComponent. For example, when i navigate to home, i want to have titleTab === Home or Actived.snapshot.data['title'] = Home
How do it? I tried many ways buti always have errors.
My spec.ts
fdescribe('TitleComponent', () => {
let component: TitleComponent;
let fixture: ComponentFixture<TitleComponent>;
let location: Location;
let router: Router;
// let route: ActivatedRoute;
const mockActivedRoute = jasmine.createSpyObj('ActivatedRoute', ['get']);
const testRouter = jasmine.createSpyObj('Router', ['get'])
const route = ({ data: of({ title: 'home' }) } as any) as ActivatedRoute;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TitleComponent,
HomeComponent,
SearchComponent,
AppComponent,
MainHomeComponent,
],
providers: [
{
provide: ActivatedRoute,
useValue: {
snapshot: {
data: { title: 'home' }
}
},
}
],
imports: [RouterTestingModule.withRoutes(routes)],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
router = TestBed.get(Router);
location = TestBed.get(Location);
router.initialNavigation();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TitleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// It doesn't works with mock
it('test home data', fakeAsync(() => {
router.navigate(['/home']);
tick();
expect(route.snapshot.data.title).toBe('title');
}));
// It doesn't works with component
//it('test ', fakeAsync(() => {
//router.navigate(['/home']);
//tick();
//expect(component.titleTab).toBe('home');
// }));
Help me please.
Router navigation is asynchronous, so my guess is that you should wait for router event to complete. Here is what I would use:
it('should....', (done) => {
router.navigate(['/home'])
.then(() => {
fixture.detectChanges();
expect(....);
done();
})
.catch(err => fail(err));
});