Testing that a method is called when component is mounted - unit-testing

I'm trying to test that a method gets called when a component is mounted but it keeps failing with
Expected mock function to have been called one time, but it was called zero times.
Here is the component:
<template>
<b-form-input
class="mr-2 rounded-0"
placeholder="Enter Search term..."
id="input-keyword"
/>
</template>
<script>
export default {
name: 'job-search-test',
methods: {
async searchJobs () {
console.log('Calling Search Jobs from JobsSearchTest')
}
},
mounted () {
this.searchJobs()
}
}
</script>
Here is the test:
import { shallowMount, createLocalVue } from '#vue/test-utils'
import BootstrapVue from 'bootstrap-vue'
import JobSearchTest from '#/components/jobs/JobSearchTest'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
describe('JobsSearchTest.vue', () => {
it('should call searchJobs method when component is mounted', () => {
const methods = {
searchJobs: jest.fn()
}
shallowMount(JobSearchTest, {
mocks: {
methods
},
localVue })
expect(methods.searchJobs).toHaveBeenCalledTimes(1)
})
})
However, the following test passes
import { shallowMount, createLocalVue } from '#vue/test-utils'
import BootstrapVue from 'bootstrap-vue'
import JobSearchTest from '#/components/jobs/JobSearchTest'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
describe('JobsSearchTest.vue', () => {
it('should call searchJobs method when component is mounted', () => {
let searchJobs = jest.fn()
shallowMount(JobSearchTest, {
methods: {
searchJobs
},
localVue })
expect(searchJobs).toHaveBeenCalledTimes(1)
})
})
According to Testing VueJs Applications by Edd Yerburgh one tests a function by stubbing it with a Jest mock the following way
it('should call $bar.start on load', () => {
const $bar = {
start: jest.fn(),
finish: () => {}
}
shallowMount(ItemList, { mocks: $bar })
expect($bar.start).toHaveBeenCalledTimes(1)
})
In my eyes, this is essentially what I am doing in the first test, which fails.
Any help with why this could be happening will be appreciated.

mocks option mocks instance properties. mocks: { methods } assumes that there's methods property in Vue component. Since this.methods.searchJobs() isn't called, the test fails.
It's searchJobs method, the test should be as the working snippet shows:
shallowMount(JobSearchTest, {
methods: {
searchJobs
},
localVue })

Related

Unit tests for alert controller in ionic

I am learning to write unit tests in ionic and am unable to write a test for AlertController. Below attached is the code
Terms.page.ts file
export class TermsPage {
constructor(private router: Router, private alertController: AlertController) {}
onAgreeClick() {
this.router.navigate(['/register']);
}
onDeclineClick() {
this.presentAlertConfirm();
}
async presentAlertConfirm() {
const alert = await this.alertController.create({
message: 'Please agree to our terms and conditions to be able to use this application!',
buttons: [
{
text: 'Agree',
cssClass: 'primary',
handler: () => {
this.onAgreeClick();
},
},
{
text: 'Maybe later',
role: 'cancel',
cssClass: 'secondry',
},
],
});
await alert.present();
}
}
Terms.spec.ts
import { DebugElement } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { Router, RouterModule } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { EmptyTestComponent } from '#test-utils';
import { TermsPage } from './terms.page';
fdescribe('TermsConditionsComponent', () => {
let component: TermsPage;
let fixture: ComponentFixture<TermsPage>;
let de: DebugElement;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TermsPage],
imports: [
RouterModule.forRoot([]),
RouterTestingModule.withRoutes([{ path: '**', component: EmptyTestComponent }]),
],
}).compileComponents();
fixture = TestBed.createComponent(TermsPage);
component = fixture.componentInstance;
de = fixture.debugElement;
router = TestBed.inject(Router);
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
it('should be able to agree and go to registration on click', async () => {
const agreeButton = de.query(By.css('#button-agree')).nativeElement as HTMLIonButtonElement;
agreeButton.click();
await fixture.whenStable();
expect(router.url).toBe('/register');
});
it('should be able to trigger popup on click of disagree click', async () => {
const disagreeButton = de.query(By.css('#button-disagree')).nativeElement as HTMLIonButtonElement;
disagreeButton.click();
await fixture.whenStable();
expect(component.presentAlertConfirm).toBeTruthy();
});
});
I need to hit the 100% coverage
Would really appreciate it if someone could help me write test case to cover the alert button actions and present. Thanks in advance
Looks like you need to split your test into two:
test #1 for alertcontroller.create usage - to has been called with proper arguments
and test #2 for button handlers
First can be easely emulated with standard jasmine calls like .toHaveBeenCalledWith(...):
const alertControllerStub = jasmine.createSpyObj('AlertController', ['create']);
...
expect(alertControllerStub.create).toHaveBeenCalledWith(options);
And the second one, you need to fire "ok"/"cancel" manually and catch the method executed for both cases
const ({buttons}) = alertControllerStub.create.calls.first().args[0];
buttons[0].handler();
expect(smth_should_called_in_handler).toHaveBeenCalled();

vue.js unit test v-slot with function call

I use bootstrap-vue for the vue.js css framework and decided to test the desired component. This component uses b-table and has a v-slot with a call function.
<template>
<b-table
striped
bordered
:items="items"
:fields="$t('pages.events.show.users.fields')"
>
<template v-slot:cell(name)="{ item }">
<b-avatar :src="item.avatar" class="mr-2" />
<span v-text="item.name" />
</template>
</b-table>
</template>
and I'm writing a simple test for this component:
import { shallowMount } from "#vue/test-utils";
import EventUsersTable from "./EventUsersTable.vue";
/* #region Test setup */
const factory = () => {
return shallowMount(EventUsersTable, {
mocks: {
$t: jest.fn()
},
stubs: {
BTable: true
}
});
};
/* #endregion */
describe("EventUsersTable.vue", () => {
let wrapper;
beforeEach(() => (wrapper = factory()));
test("should render component", () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
and i have error with this content: [Vue warn]: Error in render: "TypeError: Cannot read property 'item' of undefined"
for write test for this component i need fix this problem.
And I have a problem with the vue unit test document, they are very limited and with few examples.
If anyone knows a source that has more examples and scenarios for vue language tests, thank you for introducing it.
After inquiring, I came up with a solution that, using mount and adding the main component, was able to solve my problem.
import { mount } from "#vue/test-utils";
import { BTable } from "bootstrap-vue";
import EventUsersTable from "./EventUsersTable.vue";
/* #region Test setup */
const factory = () => {
return mount(EventUsersTable, {
mocks: {
$t: jest.fn()
},
stubs: {
BTable
}
});
};
/* #endregion */
describe("EventUsersTable.vue", () => {
let wrapper;
beforeEach(() => (wrapper = factory()));
// FIXME: fix this bug for render component
test("should render component", () => {
expect(wrapper.html()).toMatchSnapshot();
});
});

How to test events on the root element of component in vue 2?

I'm writing unit tests for the following component:
<template>
<sub-component
#foo="bar"
/>
</template>
<script>
import SubComponent from './SubComponent';
export default {
name: 'MyComponent',
components: { SubComponent },
methods: {
bar(payload) {
this.$emit('baz', ...payload);
}
}
}
</script>
And the test would be:
import { shallowMount } from '#vue/test-utils';
import _ from 'lodash';
import MyComponent from '../../components/MyComponent';
describe('MyComponent.vue', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(MyComponent);
});
it('should emit baz on subcomponent foo', () => {
const subComp = wrapper.find('sub-component-stub');
expect(subComp.exists()).toBe(true); // passes
subComp.vm.$emit('foo');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().baz).toBeTruthy(); // does not pass;
// upon logging:
console.log(_.isEqual(wrapper, subComp)); // => true
})
})
})
The example is oversimplified, but the principle here is I want a reusable <sub-component> (a modal) and various functional wrappers around it (related to one particular task the modal type performs) which map additional functionality. I don't want the functionality in the parent components, as it would violate DRY - i'd have to place it in each component containing a particular type of modal.
This would work fine if <sub-component> was not the direct child of <template>. Somehow, it appears wrapper and subComp are hosted on the same element.
How should this be tested properly?
Another possibility it's to find your element in the dom and check the emitted value of your root component.
import { shallowMount } from '#vue/test-utils'
import MyComponent from './MyComponent.vue'
import SubComponent from './SubComponent.vue'
describe('MyComponent', () => {
it('should emit baz on subcomponent foo', () => {
const wrapper = shallowMount(MyComponent)
const subComponent = wrapper.find(SubComponent)
expect(subComponent.exists()).toBe(true)
expect(wrapper.emitted('baz')).toBeUndefined()
subComponent.vm.$emit('foo', ['hello'])
expect(wrapper.emitted('baz')[0]).toEqual(['hello'])
// or expect(wrapper).toEmit('baz', 'hello') cf. below for toEmit
})
})
If you want a custom matcher for Jest:
toEmit(received, eventName, data) {
if (data) {
expect(received.emitted()[eventName][0]).toEqual([data])
} else {
expect(received.emitted()[eventName][0]).toEqual([])
}
return { pass: true }
}

vue-test-utils: How to test logic within mounted() lifecycle hook (with vuex)?

I'm trying to write a unit test for the logic within Vue's mounted() lifecycle hook, but not having much luck. The problem seems to be that mounted() never gets called when the component is mounted using vue-test-utils mount. Here's the Vue component I'm trying to test:
<template>
<div></div>
</template>
<script>
export default {
name: 'MyComponent',
mounted () {
this.$store.dispatch('logout')
}
}
</script>
And the test itself:
import { mount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import MyComponent from '#/components/MyComponent'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('MyComponent.vue', () => {
let store
let actions
beforeEach(() => {
actions = {
logout: jest.fn().mockName('logout')
}
store = new Vuex.Store({
state: {},
actions
})
})
it('calls store "logout" action', () => {
mount(MyComponent, { localVue, store })
expect(actions.logout).toHaveBeenCalled()
})
})
However, this fails with expect(logout).toHaveBeenCalled() asserting false.
If I call the mocked store action directly with actions.logout() the test passes, and I have other tests which also call store actions on things like a button press, and those pass as well, so the problem definitely appears to be with the mounted() lifecycle hook.
Any thoughts?
(vue 2.5.4 and vue-test-utils 1.0.0-beta-.15)
Not sure how it's any different, but I abstracted the store mock to another file and everything seems to work now.
mocks.js
export const storeMock = Object.freeze({
state: {},
actions: {
logout: jest.fn().mockName('logout')
},
})
test.spec.js
import { shallowMount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import { storeMock } from './mocks.js'
import MyComponent from '#/components/MyComponent'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('MyComponent.vue', () => {
let options
beforeEach(() => {
jest.clearAllMocks()
const store = new Vuex.Store(storeMock)
options = { store, localVue }
})
it('calls store "logout" action', () => {
shallowMount(MyComponent, options)
expect(storeMock.actions.logout).toHaveBeenCalled()
})
})
Without abstracting the store mock to another file, and slightly different approach without beforeEach (ruined my tests for some reason).
import { createLocalVue, shallowMount } from "#vue/test-utils";
import Vuex from "vuex";
import MyComponent from "#/components/MyComponent.vue";
describe("MyComponent", () => {
const localVue = createLocalVue();
localVue.use(Vuex);
const actions = {
logout: jest.fn()
};
const store = new Vuex.Store({ actions });
const wrapper = shallowMount(MyComponent, {
localVue,
store
});
it('calls store "logout" action', () => {
expect(actions.logout).toHaveBeenCalled();
});
});

angular2 test, how do I mock sub component

How do I mock sub component in jasmine tests?
I have MyComponent, which uses MyNavbarComponent and MyToolbarComponent
import {Component} from 'angular2/core';
import {MyNavbarComponent} from './my-navbar.component';
import {MyToolbarComponent} from './my-toolbar.component';
#Component({
selector: 'my-app',
template: `
<my-toolbar></my-toolbar>
{{foo}}
<my-navbar></my-navbar>
`,
directives: [MyNavbarComponent, MyToolbarComponent]
})
export class MyComponent {}
When I test this component, I do not want to load and test those two sub components; MyNavbarComponent, MyToolbarComponent, so I want to mock it.
I know how to mock with services using provide(MyService, useClass(...)), but I have no idea how to mock directives; components;
beforeEach(() => {
setBaseTestProviders(
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
);
//TODO: want to mock unnecessary directives for this component test
// which are MyNavbarComponent and MyToolbarComponent
})
it('should bind to {{foo}}', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(MyComponent).then((fixture) => {
let DOM = fixture.nativeElement;
let myComponent = fixture.componentInstance;
myComponent.foo = 'FOO';
fixture.detectChanges();
expect(DOM.innerHTML).toMatch('FOO');
});
});
Here is my plunker example;
http://plnkr.co/edit/q1l1y8?p=preview
As requested, I'm posting another answer about how to mock sub components with input/output:
So Lets start by saying we have TaskListComponent that displays tasks, and refreshes whenever one of them is clicked:
<div id="task-list">
<div *ngFor="let task of (tasks$ | async)">
<app-task [task]="task" (click)="refresh()"></app-task>
</div>
</div>
app-task is a sub component with the [task] input and the (click) output.
Ok great, now we want to write tests for my TaskListComponent and of course we don't want to test the real app-taskcomponent.
so as #Klas suggested we can configure our TestModule with:
schemas: [CUSTOM_ELEMENTS_SCHEMA]
We might not get any errors at either build or runtime, but we won't be able to test much other than the existence of the sub component.
So how can we mock sub components?
First we'll define a mock directive for our sub component (same selector):
#Directive({
selector: 'app-task'
})
class MockTaskDirective {
#Input('task')
public task: ITask;
#Output('click')
public clickEmitter = new EventEmitter<void>();
}
Now we'll declare it in the testing module:
let fixture : ComponentFixture<TaskListComponent>;
let cmp : TaskListComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TaskListComponent, **MockTaskDirective**],
// schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: TasksService,
useClass: MockService
}
]
});
fixture = TestBed.createComponent(TaskListComponent);
**fixture.autoDetectChanges();**
cmp = fixture.componentInstance;
});
Notice that because the generation of sub component of the fixture is happening asynchronously after its creation, we activate its autoDetectChanges feature.
In our tests, we can now query for the directive, access its DebugElement's injector, and get our mock directive instance through it:
import { By } from '#angular/platform-browser';
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;
[This part should usually be in the beforeEach section, for cleaner code.]
From here, the tests are a piece of cake :)
it('should contain task component', ()=> {
// Arrange.
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
// Assert.
expect(mockTaskEl).toBeTruthy();
});
it('should pass down task object', ()=>{
// Arrange.
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;
// Assert.
expect(mockTaskCmp.task).toBeTruthy();
expect(mockTaskCmp.task.name).toBe('1');
});
it('should refresh when task is clicked', ()=> {
// Arrange
spyOn(cmp, 'refresh');
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;
// Act.
mockTaskCmp.clickEmitter.emit();
// Assert.
expect(cmp.refresh).toHaveBeenCalled();
});
If you use schemas: [CUSTOM_ELEMENTS_SCHEMA]in TestBed the component under test will not load sub components.
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { TestBed, async } from '#angular/core/testing';
import { MyComponent } from './my.component';
describe('App', () => {
beforeEach(() => {
TestBed
.configureTestingModule({
declarations: [
MyComponent
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
});
it(`should have as title 'app works!'`, async(() => {
let fixture = TestBed.createComponent(MyComponent);
let app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('Todo List');
}));
});
This works in the released version of Angular 2.0.
Full code sample here.
An alternative to CUSTOM_ELEMENTS_SCHEMA is NO_ERRORS_SCHEMA
Thanks to Eric Martinez, I found this solution.
We can use overrideDirective function which is documented here,
https://angular.io/docs/ts/latest/api/testing/TestComponentBuilder-class.html
It takes three prarmeters;
1. Component to implement
2. Child component to override
3. Mock component
Resolved solution is here at http://plnkr.co/edit/a71wxC?p=preview
This is the code example from the plunker
import {MyNavbarComponent} from '../src/my-navbar.component';
import {MyToolbarComponent} from '../src/my-toolbar.component';
#Component({template:''})
class EmptyComponent{}
describe('MyComponent', () => {
beforeEach(injectAsync([TestComponentBuilder], (tcb) => {
return tcb
.overrideDirective(MyComponent, MyNavbarComponent, EmptyComponent)
.overrideDirective(MyComponent, MyToolbarComponent, EmptyComponent)
.createAsync(MyComponent)
.then((componentFixture: ComponentFixture) => {
this.fixture = componentFixture;
});
));
it('should bind to {{foo}}', () => {
let el = this.fixture.nativeElement;
let myComponent = this.fixture.componentInstance;
myComponent.foo = 'FOO';
fixture.detectChanges();
expect(el.innerHTML).toMatch('FOO');
});
});
I put together a simple MockComponent module to help make this a little easier:
import { TestBed } from '#angular/core/testing';
import { MyComponent } from './src/my.component';
import { MockComponent } from 'ng2-mock-component';
describe('MyComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
MyComponent,
MockComponent({
selector: 'my-subcomponent',
inputs: ['someInput'],
outputs: [ 'someOutput' ]
})
]
});
let fixture = TestBed.createComponent(MyComponent);
...
});
...
});
It's available at
https://www.npmjs.com/package/ng2-mock-component.