wrapper.find() is throwing error in unit testing - unit-testing

I'm trying to do a unit test on a Vue component. I'm getting an error when wrapper.find() is used.
Component is as given below:
snackbar.vue
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
visibility: "snackbar/visibility",
type: "snackbar/type",
message: "snackbar/message"
})
},
watch: {
visibility(value) {
if (value) {
$("#snackbar").addClass("show " + this.type);
setTimeout(() => {
$("#snackbar").removeClass("show " + this.type);
this.$store.dispatch("snackbar/close");
}, 3000);
}
}
}
};
</script>
<template>
<div id="snackbar">{{ message }}</div>
</template>
In the testing I want to get a div having snackbar as id using wrapper.find().
It's spec file:
snackbar.spec.js
import SnackBar from '../../../src/modules/Common/_components/snackbar.vue';
import { mount, createLocalVue } from '#vue/test-utils';
import Vue from 'vue'
import Vuex from 'vuex'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('Snackbar component', () => {
let store
beforeEach(() => {
let state = {
isVisible: false,
message: '',
type: ''
}
let getters = {
'snackbar/visibility': (state) => state.isVisible,
'snackbar/type': (state) => state.type,
'snackbar/message': (state) => state.message
}
store = new Vuex.Store({
modules: {
snackbar: {
state,
getters
}
}
})
})
it('renders the correct markup', () => {
let wrapper = mount(SnackBar, { localVue, store })
let snackbar = wrapper.find('#snackbar');
// some test code related to snackbar
})
})
The log is as given below:
cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run
30 05 2018 18:18:57.847:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
30 05 2018 18:18:57.849:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
30 05 2018 18:18:57.855:INFO [launcher]: Starting browser PhantomJS
30 05 2018 18:18:58.293:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket UwfYAt7yHauyEGfNAAAA with id 26585183
Snackbar component
✗ renders the correct markup
undefined is not a function (evaluating 'vNodes.findIndex(function (node) { return vNode.elm === node.elm; })')
webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2887:48 <- index.js:145115:83
filter#[native code]
removeDuplicateNodes#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2887:0 <- index.js:145115:23
findVNodesBySelector#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2917:0 <- index.js:145145:30
findVnodes#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2934:0 <- index.js:145162:30
find#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:2982:0 <- index.js:145210:27
find$$1#webpack:///node_modules/#vue/test-utils/dist/vue-test-utils.js:3272:0 <- index.js:145500:19
webpack:///test/unit/specs/snackbar.spec.js:38:32 <- index.js:142013:32
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.622 secs / 0.009 secs)

This is a problem with an old version of vue-test-utils. findIndex is not supported in IE, so we have removed findIndex from recent versions.
If you cannot update to the latest #vue/test-utils, you can add a findIndex polyfill before you run the tests—https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex#Polyfill.

Related

Vue.js (2) actions coverage incomplete after unit test

using Vue.js 2 , with vue-resource
after running correctly this unit test, the coverage is incomplete as I can see in lcov-report/src/vuex/actions.js.html ..,
only
return api.addNewShoppingList(shoppinglist)
is executed (1x), not the code inside the .then() block
thanks to anyone who can give me some feedback on this issue? ( if it's an issue.. or normal behavior )
actions.spec.js
import actions from '#/vuex/actions'
import * as types from '#/vuex/mutation_types'
describe('actions.js', () => {
var server, store, lists, successPost
successPost = {'post': true}
beforeEach(() => {
// mock shopping lists
lists = [{
id: '1',
title: 'Groceries'
}, {
id: '2',
title: 'Clothes'
}]
// mock store commit and dispatch methods
store = {
commit: (method, data) => {},
dispatch: () => {
return Promise.resolve() // static method
},
state: {
shoppinglists: lists
}
}
sinon.stub(store, 'commit')
// mock server
server = sinon.fakeServer.create()
server.respondWith('POST', /shoppinglists/, xhr => {
xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify(successPost))
})
server.autoRespond = true
})
afterEach(() => {
store.commit.restore()
server.restore()
})
describe('createShoppingList', () => {
it('should return successful POST response', () => {
let newList = { title: 'new list', id: '3' }
actions.createShoppingList(store, newList).then((resp) => {
expect(resp.body).to.eql(successPost)
})
})
})
})
actions.js
import * as types from './mutation_types'
import api from '../api'
import getters from './getters'
export default {
populateShoppingLists: ({ commit }) => {
return api.fetchShoppingLists().then(response => {
commit(types.POPULATE_SHOPPING_LISTS, response.data)
})
},
createShoppingList: (store, shoppinglist) => {
return api.addNewShoppingList(shoppinglist)
.then(() => {
store.commit(types.ADD_SHOPPING_LIST, shoppinglist)
store.dispatch('populateShoppingLists')
})
},
}
api/index.js
import Vue from 'vue'
import VueResource from 'vue-resource'
Vue.use(VueResource)
const ShoppingListsResource = Vue.resource('http://localhost:3000/' + 'shoppinglists{/id}')
export default {
addNewShoppingList: (data) => {
return ShoppingListsResource.save(data)
}
}
mutations.js
import * as types from './mutation_types'
import getters from './getters'
import _ from 'underscore'
export default {
[types.POPULATE_SHOPPING_LISTS] (state, lists) {
state.shoppinglists = lists
},
[types.ADD_SHOPPING_LIST] (state, newList) {
if (_.isObject(newList)) {
state.shoppinglists.push(newList)
}
}
}
mutations.types
export const POPULATE_SHOPPING_LISTS = 'POPULATE_SHOPPING_LISTS'
export const ADD_SHOPPING_LIST = 'ADD_SHOPPING_LIST'
getters.js
import _ from 'underscore'
export default {
getLists: state => state.shoppinglists,
}
console
mymac:lcov-report yves$ npm run unit
> shopping-list#1.0.0 unit /Users/yves/Developments/shopping-list
> cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run
[karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
[launcher]: Launching browser PhantomJS with unlimited concurrency
launcher]: Starting browser PhantomJS
[PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket -XTe5WWPBnSfAtg_AAAA with id 73492961
actions.js
...
createShoppingList
✓ should return successful POST response
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 13 of 13 SUCCESS (0.086 secs / 0.034 secs)
TOTAL: 13 SUCCESS
=============================== Coverage summary ==================
Statements : 64.44% ( 29/45 )
Branches : 50% ( 2/4 )
Functions : 81.25% ( 13/16 )
Lines : 64.44% ( 29/45 )
================================================================
lcov-report/src/vuex/actions.js.html
import * as types from './mutation_types'
import api from '../api'
import getters from './getters'
export default {
createShoppingList: (store, shoppinglist) => {
**1× return api.addNewShoppingList(shoppinglist)**
.then(() => {
store.commit(types.ADD_SHOPPING_LIST, shoppinglist)
store.dispatch('populateShoppingLists')
})
},
}

Strange Unit-Test behaviour in angular-cli generated application

I created an application with angular-cli (latest version version 1.0.0-beta.25.5) and added a simple service:
import { Injectable } from '#angular/core';
import {Headers, Http} from '#angular/http';
import 'rxjs/add/operator/toPromise';
import {Todo} from './todo';
#Injectable()
export class TodoService {
public todoUrl = 'http://localhost:4200/app/todos'; // URL to web api
constructor(public http: Http) {
}
getTodos(): Promise<Todo[]> {
return this.http.get(this.todoUrl)
.toPromise()
.then(response => response.json().data as Todo[])
.catch(this.handleError);
}
handleError(error: any): Promise<any> {
return Promise.reject(error.message || error);
}
}
with a test for it:
import {TestBed, inject} from '#angular/core/testing';
import { TodoService } from './todo.service';
import {Todo} from './todo';
import {BaseRequestOptions, Http} from '#angular/http';
import {MockBackendService} from './mock-backend.service';
import {MockBackend} from '#angular/http/testing';
describe('TodoService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
new MockBackendService().SetUpTodoBackend(backend);
return new Http(backend, options);
}
},
TodoService
]
});
});
it('should return array of todos', inject([TodoService], (service: TodoService) => {
service.getTodos().then((todos: Todo[]) => {
console.log(todos.length);
expect(todos.length).toEqual(100);
expect(todos[0].id).toEqual(11);
console.log('Finished.');
});
}));
});
In MockBackendService I return an array of 10 Todos, so the test should fail as it expects the array to contain 100 todos. When I run my test I get the following result:
LOG: 'mockConnection url:: http://localhost:4200/app/todos'
LOG: 10
LOG: 'Finished.'
Executed 1 of 1 SUCCESS (0.139 secs / 0.126 secs)
So it logs that the array contains 10 results but still passes the test. When I add 2 more "tests":
it('should pass', () => {});
it('should also pass', () => {});
the last test fails with the correct message from my first test:
TodoService should also pass FAILED
Expected 10 to equal 100.
#webpack:///src/app/todo.service.spec.ts:32:6 <- src/test.ts:70258:13 [ProxyZone]
ProxyZoneSpec</ProxyZoneSpec.prototype.onInvoke#webpack:///~/zone.js/dist/proxy.js:79:0 <- src/test.ts:54312:20 [ProxyZone]
Zone$1</Zone</Zone.prototype.run#webpack:///~/zone.js/dist/zone.js:113:0 <- src/test.ts:74539:24 [ProxyZone => ProxyZone]
scheduleResolveOrReject/<#webpack:///~/zone.js/dist/zone.js:535:0 <- src/test.ts:74961:52 [ProxyZone]
ProxyZoneSpec</ProxyZoneSpec.prototype.onInvokeTask#webpack:///~/zone.js/dist/proxy.js:103:0 <- src/test.ts:54336:20 [ProxyZone]
Zone$1</ZoneDelegate</ZoneDelegate.prototype.invokeTask#webpack:///~/zone.js/dist/zone.js:274:0 <- src/test.ts:74700:21 [ProxyZone]
Zone$1</Zone</Zone.prototype.runTask#webpack:///~/zone.js/dist/zone.js:151:0 <- src/test.ts:74577:28 [<root> => ProxyZone]
drainMicroTaskQueue#webpack:///~/zone.js/dist/zone.js:433:0 <- src/test.ts:74859:25 [<root>]
Executed 3 of 3 (1 FAILED) (0.344 secs / 0.133 secs)
What am I doing wrong here to get such a strange behaviour? I can recreate that behaviour with a newly generated application, so it seems to be somewhere in my code or in a library I use...

Testing that a component behaves properly when the service it calls throws an exception with Angular 2

In my Angular 2 application, I'm trying to unit test the following component:
export class LoginComponent implements OnInit {
invalidCredentials = false;
unreachableBackend = false;
constructor(private authService: AuthService) {}
ngOnInit() {
this.invalidCredentials = false;
}
onSubmit(user: any) {
this.authService.authenticateUser(<User>user).subscribe((result) => {
if (!result) {
this.invalidCredentials = true;
}
}, (error) => {
if (error instanceof InvalidCredentialsError) {
this.invalidCredentials = true;
} else {
this.unreachableBackend = true;
}
});
}
}
I have already successfully tested the happy path. Now I would like to check that when authService.authenticateUser() throws an error, invalidCredentials and unreachableBackend are correctly set. Here is what I am trying:
describe('Authentication triggering an error', () => {
class FakeAuthService {
authenticateUser(user: User) {
throw new InvalidCredentialsError();
}
}
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let authService: AuthService;
let spy: Spy;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent],
providers: [
{provide: AuthService, useClass: FakeAuthService},
{provide: TranslateService, useClass: FakeTranslateService}
],
imports: [ FormsModule, TranslateModule, AlertModule ]
});
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
authService = fixture.debugElement.injector.get(AuthService);
spy = spyOn(authService, 'authenticateUser').and.callThrough();
});
it('should not log in successfully if authentication fails', () => {
const user = {username: 'username', password: 'password'};
component.onSubmit(user);
expect(authService.authenticateUser).toHaveBeenCalledWith(user);
expect(spy.calls.count()).toEqual(1, 'authenticateUser should have been called once');
expect(component.invalidCredentials).toBe(true, 'credentials should be invalid because of the exception');
expect(component.unreachableBackend).toBe(false, 'backend should be reachable at first');
});
});
But when I run this test, I get the following failure:
PhantomJS 2.1.1 (Mac OS X 0.0.0) Component: Login Authentication triggering an error should not log in successfully if authentication fails FAILED
[object Object] thrown in src/test.ts (line 49782)
authenticateUser#webpack:///Users/sarbogast/dev/myproject/frontend/src/app/login/login.component.spec.ts:112:44 <- src/test.ts:49782:67
onSubmit#webpack:///Users/sarbogast/dev/myproject/frontend/src/app/login/login.component.ts:9:4896 <- src/test.ts:85408:5955
webpack:///Users/sarbogast/dev/myproject/frontend/src/app/login/login.component.spec.ts:139:25 <- src/test.ts:49806:31
invoke#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:203:0 <- src/test.ts:84251:33
onInvoke#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/proxy.js:72:0 <- src/test.ts:59204:45
invoke#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:202:0 <- src/test.ts:84250:42
run#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:96:0 <- src/test.ts:84144:49
webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:91:27 <- src/test.ts:58940:53
execute#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:119:0 <- src/test.ts:58968:46
execute#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:119:0 <- src/test.ts:58968:46
invokeTask#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:236:0 <- src/test.ts:84284:42
runTask#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:136:0 <- src/test.ts:84184:57
drainMicroTaskQueue#webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:368:0 <- src/test.ts:84416:42
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 33 of 38 (1 FAILED) (skipped 5) (0.704 secs / 0.842 secs)
So obviously there is something I didn't get. I should mention the fact that I'm completely new to JS unit testing and somewhat new to reactive programming.
You can't do that as the error will just be thrown and bubble up to the test. What you need to do is allow the user the subscribe with callbacks, and then you can call the error callback. For example
class FakeAuthService {
authenticateUser(user: User) {
// return this service so user can call subscribe
return this;
}
subscribe(onNext, onError) {
if (onError) {
onError(new InvalidCredentialsError());
}
}
}
Another option is to use Observable.throw(new InvalidCredentialsError())
authenticateUser(user: User) {
return Observable.throw(new InvalidCredentialsError());
}
This will cause any subscriber's onError callback to be called. Personally I like using the mock returning itself. I can make the mock more configurable to make testing easier. See the link below for an example of what I mean.
See also:
Angular 2 - Testing error case with observables in services

Angular 2 unit test parsing error using webpack

I'm getting the follow error when I run Angular 2 unit test using webpack:
FAILED TESTS:
Other Trims Tested
✖ should display other trims articles
PhantomJS 2.1.1 (Linux 0.0.0)
Error: Template parse errors:
Unexpected closing tag "div" ("module.exports = "<div *ngFor=\"let trim of otherTrims\">\n {{ trim.name }}\n[ERROR ->]</div>\n\n<!--<div class='test-name'>{{ otherTrims[0].name }}</div>-->\n";"): OtherTrimsTestedComponent#0:78 in /app/config/spec-bundle.js (line 52805)
I see that it is testing webpacks bundle-js file but I am not sure if there is another import I have to do from angular core tests
I have my HTML setup as:
<div *ngFor="let trim of otherTrims">
{{ trim.name }}
</div>
and spec test as:
import {
it,
fdescribe,
expect,
inject,
async,
TestComponentBuilder
} from '#angular/core/testing';
import { OtherTrimsTestedComponent } from './other-trims-tested.component';
let mockData = [
{
'featured_image': 'http://test.url.com',
'article_type': 'Test',
'article_url': 'http://test.url.com',
'name': 'Ford F-150 Raptor',
'specs' : '4 Door AWD Pickup Truck, 150Bhp, 285 lb-ft, 8-sp Automatic',
'msrp': '50,000'
}
];
fdescribe('Other Trims Tested', () => {
it('should have a selector', () => {
let annotations = Reflect.getMetadata('annotations', OtherTrimsTestedComponent);
expect(annotations[0].selector).toBe('other-trims-tested');
});
it('should display other trims articles', async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(OtherTrimsTestedComponent).then(fixture => {
// Get components
let otherTrimsTestedComponent = fixture.componentInstance; // Get component instance
let element = fixture.nativeElement; // Get test component elements
otherTrimsTestedComponent.otherTrims = mockData;
// Detect changes? (How?)
fixture.detectChanges();
// Test against data
expect(element.querySelector('.test-name').innerText).toBe('Ford F-150 Raptor2');
});
})));
});
and Component:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'other-trims-tested',
template: require('./other-trims-tested.component.html')
})
export class OtherTrimsTestedComponent implements OnInit {
#Input() otherTrims: any[];
constructor() { }
ngOnInit() { }
}
Any hints/articles can help. Thanks
Have you added html-loader to your webpack test configuration?
rules: [
...
{
test: /\.html$/,
loader: 'raw-loader'
}
...
}

HowTo test Modal emitter in Angular2?

This is my class component:
export class GoalSettingsPage {
public goal: Goal;
dismiss() {
this.viewCtrl.dismiss();
}
}
This is my test:
it('should emit on click', () => {
let g = new Goal()
let settingModal = Modal.create(GoalSettingsPage, { g:Goal });
settingModal.subscribe(hc => {
console.log(hc);
expect(hc).toEqual({hasCompleted:true});
});
settingModal.dismiss()
})
This is my error:
04 07 2016 16:36:48.438:ERROR [Chrome 51.0.2704 (Mac OS X 10.11.3) | Goal Settings | should emit on click]: TypeError: Cannot read property 'remove' of undefined
at Modal.ViewController.dismiss (http://localhost:9876/absolute/var/folders/zb/tpysrhsx7hbg1dnsn4gwtqq00000gn/T/65a6294f711f882f7429da26bc12e438.browserify?2c82fe582a9911b998cb77bce54ea6804460d5cd:59060:25)
Any idea what I am doing wrong?