I am trying to test the following App.vue component when a click event is fired on the logout vue-router link...
App.vue
<template>
<div id="app">
<header id="header">
<nav>
<ul class="navigation">
<li id="home"><router-link :to="{ name: 'home' }">Home</router-link></li>
<li id="login" v-if="!isAuthenticated"><router-link :to="{ name: 'login' }">Login</router-link></li>
<li id="shoppinglists" v-if="isAuthenticated"><router-link :to="{ name: 'shoppinglists' }" >Shopping Lists</router-link></li>
<li id="logout" v-if="isAuthenticated">Logout</li>
</ul>
</nav>
</header><!-- /#header -->
<section id="page">
<router-view></router-view>
</section><!-- /#page -->
</div>
</template>
<script>
import store from '#/vuex/store'
import router from '#/router/index'
import { mapGetters } from 'vuex'
export default {
name: 'app',
computed: {
...mapGetters({ isAuthenticated: 'isAuthenticated' })
},
methods: {
logout () {
this. $store.dispatch('logout')
.then(() => {
window.localStorage.removeItem('vue-authenticate.vueauth_token')
this/$router.push({ name: 'home' })
})
}
},
store,
router
}
</script>
To test the logout click, I preset the isAuthenticated state to true, so the logout router link show up and I trigger the click event on it.
LOG: 'navigation: ', <ul class="navigation"><li id="home">
Home</li> <!---->
<li id="shoppinglists">Shopping Lists
</li> <li id="logout">Logout</li></ul>
I expect the action logout to have been called .. but it's not ... why ?
App.spec.js
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import App from '#/App'
import router from '#/router/index'
import { mount } from 'avoriaz'
import sinon from 'sinon'
Vue.use(Vuex)
Vue.use(VueRouter)
describe('App.vue', () => {
let actions
let getters
let store
beforeEach(() => {
getters = {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
actions = {
logout: sinon.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
getters,
actions,
state: {
isAuthenticated: true,
currentUserId: ''
}
})
router
})
it('calls logout method', () => {
const wrapper = mount(App, { router, store })
console.log('navigation: ', wrapper.find('ul.navigation')[0].element)
const logoutLink = wrapper.find('#logout a')[0]
logoutLink.trigger('click')
expect(actions.logout.calledOnce).to.equal(true)
})
})
vuex/actions.js
import { IS_AUTHENTICATED, CURRENT_USER_ID } from './mutation_types'
import getters from './getters'
export default {
logout: ({commit}) => {
commit(IS_AUTHENTICATED, { isAuthenticated: false })
commit(CURRENT_USER_ID, { currentUserId: '' })
return true
}
}
vuex/mutations.js
import * as types from './mutation_types'
import getters from './getters'
export default {
[types.IS_AUTHENTICATED] (state, payload) {
state.isAuthenticated = payload.isAuthenticated
},
[types.CURRENT_USER_ID] (state, payload) {
state.currentUserId = payload.currentUserId
}
}
vuex/getters.js
export default {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
Finally , I found a way to test it :
import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import App from '#/App'
import router from '#/router/index'
import { mount } from 'avoriaz'
import sinon from 'sinon'
Vue.use(Vuex)
Vue.use(VueRouter)
describe('App.vue', () => {
let actions
let getters
let store
let sandbox
let routerPush
beforeEach(() => {
sandbox = sinon.sandbox.create()
getters = {
isAuthenticated: (state) => {
return state.isAuthenticated
}
}
actions = {
logout: sandbox.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
getters,
state: {
isAuthenticated: true,
currentUserId: ''
},
actions
})
router
})
afterEach(() => {
sandbox.restore()
})
it('calls logout method', (done) => {
const wrapper = mount(App, { store, router })
routerPush = sandbox.spy(wrapper.vm.$router, 'push')
const logoutLink = wrapper.find('#logout a')[0]
logoutLink.trigger('click')
wrapper.vm.$nextTick(() => {
expect(actions.logout.calledOnce).to.equal(true)
actions.logout().then(() => {
expect(routerPush).to.have.been.calledWith('/home')
})
done()
})
})
})
Related
I'm create user login page in vue js and consuming data from django with axios. I have utilized jwt to create token session in client-side
The problem is the session is not saved when the page is refreshed. I have frustated because it. This is my source code :
In '../src/store/modules/auth.js'
import Vue from 'vue'
import Axios from 'axios'
import 'es6-promise/auto'
// In order that Axios work nice with Django CSRF
Axios.defaults.xsrfCookieName = 'csrftoken'
Axios.defaults.xsrfHeaderName = 'X-CSRFToken'
const state = {
authUser: {},
users: [],
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/api/auth/obtain_token/',
refreshJWT: 'http://127.0.0.1:8000/api/auth/refresh_token/',
baseUrl: 'http://127.0.0.1:8000/api/auth/',
register: 'http://127.0.0.1:8000/signup/'
}
}
const mutations = {
setAuthUser: (state, {
authUser,
isAuthenticated
}) => {
Vue.set(state, 'authUser', authUser)
Vue.set(state, 'isAuthenticated', isAuthenticated)
},
updateToken: (state, newToken) => {
localStorage.setItem('token', newToken);
state.jwt = newToken;
},
removeToken: (state) => {
localStorage.removeItem('token');
state.jwt = null;
},
}
const actions = {
refreshToken(){
const payload = {
token: this.state.jwt
}
Axios.post(state.endpoints.refreshJWT, payload)
.then((response)=>{
this.commit('updateToken', response.data.token)
})
.catch((error)=>{
console.log(error)
})
}
}
export default {
state,
mutations,
actions,
}
In '../src/store/index.js'
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import auth from './modules/auth'
Vue.use(Vuex)
// Make Axios play nice with Django CSRF
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
export default new Vuex.Store({
modules: {
auth
},
})
In '../src/components/login.vue'
<template>
<div class="login">
<form>
<label for="username">Username</label>
<input
type="text"
name="username"
v-model="username"
/><br>
<label for="password">Password</label>
<input
type="password"
name="password"
v-model="password"
/><br>
<input
type="button"
#click="login()"
value="Login"
/>
</form>
</template>
<script>
import axios from 'axios'
/* eslint-disable */
export default {
name: 'Login',
data(){
return {
username: '',
password: ''
}
},
methods: {
login(){
const payload = {
username: this.username,
password: this.password
}
axios.post(this.$store.state.auth.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
this.$store.commit('setAuthUser',
{
authUser: response.data,
isAuthenticated: true
}
)
this.$router.push({path: 'dashboard-user/id/list-vendor'})
})
.catch((error) => {
//NOTE: erase this when production
console.log(error);
console.debug(error);
console.dir(error);
alert("The username or password is incorrect");
})
}
}
}
</script>
In 'main.js'
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import 'tachyons'
import routes from './routes'
import './styles.css'
import store from '#/store'
Vue.config.productionTip = false
Vue.use(VueRouter)
import '#/assets/fonts/all.css';
const router = new VueRouter({
mode: 'history',
routes
})
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Since use register auth as a module, you should use store.state.auth.jwt instead of store.state.jwt
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.auth.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.auth.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})
Is there any way to unit test the navigation guards in a router file ?
Could not find any post or link on this topic ... ant tips, trick or feedback welcome..
Here is the router/index.js , and I would like to test the router.beforeEach()
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '#/pages/HomePage'
import Login from '#/pages/LoginPage'
import ShoppingLists from '#/pages/ShoppingListsPage'
import vueAuthInstance from '../services/auth.js'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: { auth: false, title: 'Home' }
},
{
path: '/login',
name: 'login',
component: Login,
meta: { auth: false, title: 'Login' }
},
{
path: '/shoppinglists',
name: 'shoppinglists',
component: ShoppingLists,
meta: { auth: true, title: 'Shopping Lists' }
},
{
path: '/logout',
name: 'logout'
}
]
})
router.beforeEach(function (to, from, next) {
if (to.meta && to.meta.title) {
document.title = to.meta.title
}
if (to.meta && to.meta.auth !== undefined) {
if (to.meta.auth) {
if (vueAuthInstance.isAuthenticated()) {
next()
} else {
router.push({ name: 'login' })
}
} else {
next()
}
} else {
next()
}
})
export default router
I found a way to do it, importing the router and using simply router.push) to navigate. I also need to stub the vueAuthInstance to authenticate or not the request
import VueRouter from 'vue-router'
import Vue from 'vue'
import sinon from 'sinon'
import router from '#/router/index'
import vueAuthInstance from '#/services/auth.js'
Vue.use(VueRouter)
describe('Router', () => {
let sandbox
beforeEach(() => {
sandbox = sinon.sandbox.create()
router
})
afterEach(() => {
sandbox.restore()
})
it('should be in history mode', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(false)
expect(router.mode).to.eql('history')
})
it('should be able to navigate without authentication', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(false)
router.push('/')
expect(router.history.current.path).to.eql('/')
expect(router.getMatchedComponents('/')[0].name).to.eql('HomePage')
router.push('/login')
expect(router.history.current.path).to.eql('/login')
expect(router.getMatchedComponents('/login')[0].name).to.eql('LoginPage')
})
it('should not be able to navigate to protected page when not authenticated', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(false)
router.push('/shoppinglists')
expect(router.history.current.path).to.eql('/login')
expect(router.getMatchedComponents('/login')[0].name).to.eql('LoginPage')
})
it('should be able to navigate to protected page when authenticated', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(true)
router.push('/shoppinglists')
expect(router.history.current.path).to.eql('/shoppinglists')
expect(router.getMatchedComponents('/shoppinglists')[0].name).to.eql('ShoppingListPage')
})
it('should be able to navigate to unprotected page when authenticated', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(true)
router.push('/home')
expect(router.history.current.path).to.eql('/home')
expect(router.getMatchedComponents('/')[0].name).to.eql('HomePage')
})
})
I am testing my App.vue , and I am stuck with a Vuex error on getters...
I guess it's related to a badly define getters property , but I don't see how to solve it ..
feeedback welcome
Console.log
ERROR LOG: '[vuex] unknown getter: getLists'
App.vue
✗ calls store action addShoppingList when a click event is fired from the plus-sign icon
AssertionError: expected false to equal true
at Context.<anonymous> (webpack:///test/unit/specs/App.spec.js:33:50 <- index.js:24490:51)
App.spec.js
import App from '#/App'
import Vue from 'vue'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('App.vue', () => {
let actions
let getters
let store
beforeEach(() => {
actions = {
addShoppingList: sinon.stub(),
populateShoppingLists: sinon.stub()
}
getters = {
shoppinglists: () => 'getLists'
}
store = new Vuex.Store({
state: {},
actions,
getters
})
})
it('calls store action addShoppingList when a click event is fired from the plus-sign icon', (done) => {
const wrapper = mount(App, { store })
wrapper.find('a')[0].trigger('click')
wrapper.vm.$nextTick(() => {
expect(actions.createShoppingList.calledOnce).to.equal(true)
done()
})
})
App.vue
<template>
<div id="app" class="container">
<ul class="nav nav-tabs" role="tablist">
<li :class="index===shoppinglists.length-1 ? 'active' : ''" v-for="(list, index) in shoppinglists" :key="list.id" role="presentation">
<shopping-list-title-component :id="list.id" :title="list.title"></shopping-list-title-component>
</li>
<li>
<a href="#" #click="addShoppingList">
<i class="glyphicon glyphicon-plus-sign"></i>
</a>
</li>
</ul>
<div class="tab-content">
<div :class="index===shoppinglists.length-1 ? 'active' : ''" v-for="(list, index) in shoppinglists" :key="list.id" class="tab-pane" role="tabpanel" :id="list.id">
<shopping-list-component :id="list.id" :title="list.title" :items="list.items"></shopping-list-component>
</div>
</div>
</div>
</template>
<script>
import ShoppingListComponent from './components/ShoppingListComponent'
import ShoppingListTitleComponent from './components/ShoppingListTitleComponent'
import store from './vuex/store'
import { mapGetters, mapActions } from 'vuex'
import _ from 'underscore'
export default {
components: {
ShoppingListComponent,
ShoppingListTitleComponent
},
computed: {
...mapGetters({ shoppinglists: 'getLists' })
},
methods: _.extend({}, mapActions(['populateShoppingLists', 'createShoppingList']), {
addShoppingList () {
let list = { title: 'New Shopping List', items: [] }
this.createShoppingList(list)
}
}),
store,
mounted: function () {
this.$nextTick(function () {
this.populateShoppingLists()
})
}
}
</script>
UPDATE
here are my getters.js anf store.js files
store.js is imported in App.vue
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters' // import getters !
import actions from './actions'
import mutations from './mutations'
Vue.use(Vuex)
const state = {
shoppinglists: []
}
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
getters.js
import _ from 'underscore'
export default {
getLists: state => state.shoppinglists,
getListById: (state, id) => {
return _.findWhere(state.shoppinglists, { id: id })
}
}
You don't define a getLists getter in your store. You're defining a shoppinglists getter that returns 'getLists'.
You need to either change your mapGetters line to be:
...mapGetters(['shoppinglists'])
Or change the name of the getter to getLists:
getters = {
getLists: () => 'getLists'
}
(Although I'm not sure if you are really meaning to return a string value in that getter or not)
#thanksd put me on tracks... see my comment
so I need to define the getters in my Vur.spec.js as following
getters = {
getLists: () => {
// console.log('WE ARE S TEST')
state => state.shoppinglists
}
}
I am trying to test the following component w Avoriaz, but upon props change , the action in watch: {} is not triggered
ItemComponent.vue
switch checkbox
✗ calls store action updateList when item checkbox is switched
AssertionError: expected false to equal true
at Context.<anonymous> (webpack:///test/unit/specs/components/ItemComponent.spec.js:35:47 <- index.js:25510:48)
thanks for feedback
ItemComponent.vue
<template>
<li :class="{ 'removed': item.checked }">
<div class="checkbox">
<label>
<input type="checkbox" v-model="item.checked"> {{ item.text }}
</label>
</div>
</li>
</template>
<script>
import { mapActions } from 'vuex'
export default {
props: ['item', 'id'],
methods: mapActions(['updateList']),
watch: {
'item.checked': function () {
this.updateList(this.id)
}
}
}
</script>
here is my component test
ItemComponent.spec.js
import Vue from 'vue'
import ItemComponent from '#/components/ItemComponent'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('ItemComponent.vue', () => {
let actions
let store
beforeEach(() => {
actions = {
updateList: sinon.stub()
}
store = new Vuex.Store({
state: {},
actions
})
})
describe('switch checkbox', () => {
it('calls store action updateList when item checkbox is switched', () => {
const id = '3'
const item = { text: 'Bananas', checked: true }
const wrapper = mount(ItemComponent, { propsData: { item, id }, store })
// switch item checked to false
wrapper.setProps({ item: { text: 'Bananas', checked: false } })
expect(wrapper.vm.$props.item.checked).to.equal(false)
expect(actions.updateList.calledOnce).to.equal(true)
})
})
})
U mistaked the prop,use :checked instead
I should write my expect(actions.updateList() . within a $nextTick block
describe('switch checkbox', () => {
it('calls store action updateList when item checkbox is switched', (done) => {
const id = '3'
const item = { text: 'Bananas', checked: true }
const wrapper = mount(ItemComponent, { propsData: { item, id }, store })
// switch item.checked to false
wrapper.setProps({ item: { text: 'Bananas', checked: false } })
expect(wrapper.vm.$props.item.checked).to.equal(false)
wrapper.find('input')[0].trigger('input')
wrapper.vm.$nextTick(() => {
expect(actions.updateList.calledOnce).to.equal(true)
done()
})
})
})
then my test is OK
ItemComponent.vue
switch checkbox
✓ calls store action updateList when item checkbox is switched
I have a listing component with following code:
///<reference path="../../node_modules/angular2/typings/browser.d.ts"/>
import { Component, OnInit } from 'angular2/core';
import { ROUTER_DIRECTIVES } from 'angular2/router';
import { Employee } from '../models/employee';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
#Component({
selector: 'employee-list',
template: `
<ul class="employees">
<li *ngFor="#employee of employees">
<a [routerLink]="['EmployeeDetail', {id: employee.id}]">
<span class="badge">{{employee.id}}</span>
{{employee.name}}
</a>
</li>
</ul>
`,
directives: [ROUTER_DIRECTIVES],
providers: [EmployeeListServiceComponent]
})
export class EmployeeListComponent implements OnInit {
public employees: Employee[];
public errorMessage: string;
constructor(
private _listingService: EmployeeListServiceComponent
){}
ngOnInit() {
this._listingService.getEmployees().subscribe(
employees => this.employees = employees,
error => this.errorMessage = <any>error
);
}
}
I wish to write unit tests for the ngOninit hook. I have written following test:
/// <reference path="../../typings/main/ambient/jasmine/jasmine.d.ts" />
import {
it,
describe,
expect,
TestComponentBuilder,
injectAsync,
setBaseTestProviders,
beforeEachProviders,
} from "angular2/testing";
import { Component, provide, ApplicationRef, OnInit } from "angular2/core";
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from "angular2/platform/testing/browser";
import {
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
ROUTER_PRIMARY_COMPONENT,
APP_BASE_HREF
} from 'angular2/router';
import {XHRBackend, HTTP_PROVIDERS} from "angular2/http";
import { MockApplicationRef } from 'angular2/src/mock/mock_application_ref';
import {MockBackend } from "angular2/src/http/backends/mock_backend";
import {Observable} from 'rxjs/Rx';
import 'rxjs/Rx';
import { Employee } from '../models/employee';
import { EmployeeListComponent } from './list.component';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
class MockEmployeeListServiceComponent {
getEmployees () {
return Observable.of([
{
"id": 1,
"name": "Abhinav Mishra"
}
]);
}
}
#Component({
template: '<employee-list></employee-list>',
directives: [EmployeeListComponent],
providers: [MockEmployeeListServiceComponent]
})
class TestMyList {}
describe('Employee List Tests', () => {
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);
beforeEachProviders(() => {
return [
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(EmployeeListServiceComponent, {useClass: MockEmployeeListServiceComponent}),
provide(XHRBackend, {useClass: MockBackend}),
provide(APP_BASE_HREF, {useValue: '/'}),
provide(ROUTER_PRIMARY_COMPONENT, {useValue: EmployeeListComponent}),
provide(ApplicationRef, {useClass: MockApplicationRef})
]
});
it('Should be true',
injectAsync([TestComponentBuilder], (tcb) => {
return tcb
.createAsync(TestMyList)
.then((fixture) => {
fixture.detectChanges();
var compiled = fixture.debugElement.nativeElement;
console.log(compiled.innerHTML);
expect(true).toBe(true);
});
})
);
});
However, the output of console.log in the test is an empty ul tag as follows:
'<employee-list>
<ul class="employees">
<!--template bindings={}-->
</ul>
</employee-list>'
Can anyone suggest me the proper way of writing unit tests for component hooks?
SOLUTION
Mock the http request in the injectAsync block as follows:
backend.connections.subscribe(
(connection:MockConnection) => {
var options = new ResponseOptions({
body: [
{
"id": 1,
"name": "Abhinav Mishra"
}
]
});
var response = new Response(options);
connection.mockRespond(response);
}
);
However now i am getting another error as follows:
Failed: EXCEPTION: Component "EmployeeListComponent" has no route config. in [['EmployeeDetail', {id: employee.id}] in EmployeeListComponent#3:7]
ORIGINAL EXCEPTION: Component "EmployeeListComponent" has no route config.
ORIGINAL STACKTRACE:
Error: Component "EmployeeListComponent" has no route config.
If you call async code in ngOnInit() you can't assume it is completed when console.log(...) is executed. this.employees is only set when the callback you passed to subscribe(...) gets called after the response arrived.
If you use MockBackend you can control the response and after the response was passed you have to run fixture.detectChanges() again to make the component re-render with the updated data, then you can read innerHTML and expect it to contain the rendered content.