How to test mutations when using vuex modules - unit-testing

Before using vuex modules , my mutation tests were OK :
import mutations from '#/vuex/mutations.js'
import vueAuthInstance from '#/services/auth.js'
import { IS_AUTHENTICATED, CURRENT_USER_ID } from '#/vuex/mutation_types.js'
describe('mutations.js', () => {
var state
beforeEach(() => {
state = {
isAuthenticated: vueAuthInstance.isAuthenticated(),
currentUserId: ''
}
})
describe('IS_AUTHENTICATED', () => {
it('should set authentication status', () => {
state.isAuthenticated = false
mutations[IS_AUTHENTICATED](state, {isAuthenticated: true})
expect(state.isAuthenticated).to.eql(true)
})
})
...
})
Now I refactored my vuex folders, my state and mutations are inside each vuex/modules/../index.js file
src
|_ vuex
| L_ modules
| L_ login
| |_ index.js
| |_ actions.js
| |_ getters.js
| |_ mutation_types.js
|_ App.vue
|_ main.js
vuex/modules/login/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import actions from './actions'
import getters from './getters'
import * as types from './mutation_types'
import vueAuthInstance from '#/services/auth.js'
Vue.use(Vuex)
const state = {
isAuthenticated: vueAuthInstance.isAuthenticated(),
currentUserId: ''
}
const mutations = {
[types.IS_AUTHENTICATED] (state, payload) {
state.isAuthenticated = payload.isAuthenticated
},
...
}
export default {
state,
mutations,
actions,
getters
}
Withe a vuex/store.js
import Vue from 'vue'
import Vuex from 'vuex'
import login from '#/vuex/modules/login'
// import other modules
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
login,
... (other modules )
}
})
To take in account this new structure, I rewrote the unit test as following :
test/unit/specs/vuex/modules/login/index.spec.js
import { mutations } from '#/vuex/modules/login/index.js'
import vueAuthInstance from '#/services/auth.js'
import types from '#/vuex/modules/login/mutation_types.js'
describe('mutations.js', () => {
var state
beforeEach(() => {
state = {
isAuthenticated: vueAuthInstance.isAuthenticated(),
currentUserId: ''
}
})
describe('IS_AUTHENTICATED', () => {
it('should set authentication status', () => {
state.isAuthenticated = false
mutations[types.IS_AUTHENTICATED](state, {isAuthenticated: true})
expect(state.isAuthenticated).to.eql(true)
})
})
})
And I get an error, on the mutation :
✗ should set authentication status
TypeError: Cannot read property 'IS_AUTHENTICATED' of undefined
I tried to change the import { mutations } statement , and import directly the store.js, in which the modules are defined, and use store._mutations,
LOG: 'MUTATIONS: ', Object{IS_AUTHENTICATED: [function wrappedMutationHandler(payload) { ... }], ...}
using store._mutations.IS_AUTHENTICATED0 , seems to work, ( don't know why an array? ..) but something is wrong with this function and the state, payload args, as the test doesn't pass
import store from '#/vuex/store'
import vueAuthInstance from '#/services/auth.js'
describe('mutations.js', () => {
var state
beforeEach(() => {
state = {
isAuthenticated: vueAuthInstance.isAuthenticated(),
currentUserId: ''
}
})
describe('IS_AUTHENTICATED', () => {
it('should set authentication status', () => {
state.isAuthenticated = false
console.log('MUTATIONS: ', store._mutations.IS_AUTHENTICATED())
store._mutations.IS_AUTHENTICATED[0](state, {isAuthenticated: true})
expect(state.isAuthenticated).to.eql(true)
})
})
...
})
1) should set authentication status
mutations.js IS_AUTHENTICATED
AssertionError: expected false to deeply equal true
I checked the passed args to the mutation in the index.js file
const mutations = {
[types.IS_AUTHENTICATED] (state, payload) {
console.log('MUTATION state: ', state)
console.log('MUTATION payload: ', payload)
state.isAuthenticated = payload.isAuthenticated
},
[types.CURRENT_USER_ID] (state, payload) {
state.currentUserId = payload.currentUserId
}
}
And. I do not see the passed arg values, it seems that the state args is the only passed value from my test :
LOG: 'MUTATION state: ', Object{isAuthenticated: false, currentUserId: ''}
LOG: 'MUTATION payload: ', Object{isAuthenticated: false, currentUserId: ''}
What's wrong with this code ? how to proceed for testing the mutations in this case, using vuex modules ?
thanks for feedback

I found a way to test the mutation using vuex modules, but I don't know if it's the best way...
my test is quite simple , using store.commit as I cannot call directly the mutation handler, and I import only the vuex/store
src/test/unit/specs/modules/login/index.js
import store from '#/vuex/store'
describe('mutations.js', () => {
describe('IS_AUTHENTICATED', () => {
it('should set authentication status', () => {
store.commit('IS_AUTHENTICATED', {isAuthenticated: true})
expect(store.state.login.isAuthenticated).to.eql(true)
})
})
...
})

Actually, this is bad approach.
You should create mock state, and use it.
import { createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import { storeModule } from '#path/to/store/modules';
const mutations = storeModule.mutations;
describe('mutations', () => {
it('testCase#1', () => {
createLocalVue().use(Vuex);
const state = {
//mock state values
};
const store = new Vuex.Store({state, mutations});
store.commit('mutationToTest', arg);
expect(state.arg).toBe(1);
})
})

Related

Unit Test in NestJs using Jest

So, I want to test a Controller, but I always get the same error, and I not really familiarized with Jest.
I work with php and I already did some unit tests in phpUnit, but with Jest I'm really having some trouble to understand how to do.
Here is my error message
● Test suite failed to run
SyntaxError: /home/webjump-nb103/Projects/development/workspace/stare/customer/src/customer/customer.controller.spec.ts: Unexpected token, expected ";" (13:26)
11 |
12 | describe('Customer Controller', () => {
> 13 | let customerController: CustomerController;
| ^
14 | let customerService: CustomerService;
15 |
16 | beforeAll(async () => {
at Parser.raise (node_modules/#babel/parser/src/parser/location.js:41:63)
at Parser.unexpected (node_modules/#babel/parser/src/parser/util.js:150:16)
at Parser.semicolon (node_modules/#babel/parser/src/parser/util.js:123:40)
at Parser.parseVarStatement (node_modules/#babel/parser/src/parser/statement.js:703:10)
at Parser.parseStatementContent (node_modules/#babel/parser/src/parser/statement.js:216:21)
at Parser.parseStatement (node_modules/#babel/parser/src/parser/statement.js:146:17)
at Parser.parseBlockOrModuleBlockBody (node_modules/#babel/parser/src/parser/statement.js:865:25)
at Parser.parseBlockBody (node_modules/#babel/parser/src/parser/statement.js:841:10)
at Parser.parseBlock (node_modules/#babel/parser/src/parser/statement.js:818:10)
at Parser.parseFunctionBody (node_modules/#babel/parser/src/parser/expression.js:1964:24)
My controller
import {Controller, HttpStatus, Post, Res, Body, ValidationPipe, Put, Param, Get} from '#nestjs/common';
import {CreateCustomerDto} from './dto/customer/customer.dto';
import {CustomerService} from './customer.service';
#Controller('customer')
export class CustomerController {
constructor(private readonly customerService: CustomerService) { }
#Post()
async createPost(#Res() res, #Body(ValidationPipe) createCustomerDto: CreateCustomerDto ) {
const customer = await this.customerService.createCustomer(createCustomerDto);
return res.status(HttpStatus.OK).json({
customer: customer
});
}
#Put('update/:id')
async createUpdate(#Param('id') id: string, #Res() res, #Body(ValidationPipe) createCustomerDto: CreateCustomerDto) {
const customer = await this.customerService.updateCustomer(createCustomerDto, id);
return res.status(HttpStatus.OK).json({
customer: customer
});
}
}
**My Service **
import {Injectable} from '#nestjs/common';
import {Model} from 'mongoose';
import {InjectModel} from '#nestjs/mongoose';
import {Customer} from './interfaces/customer.interface';
import {CreateCustomerDto} from './dto/customer/customer.dto';
#Injectable()
export class CustomerService {
constructor(#InjectModel('customer') private readonly customerModel: Model<Customer>) {}
async createCustomer(createCustomerDto: CreateCustomerDto ): Promise<Customer> {
const customer = new this.customerModel(createCustomerDto);
return customer.save();
}
async updateCustomer(createCustomerDto: CreateCustomerDto, id: string): Promise<Customer> {
const customer = await this.customerModel
.findByIdAndUpdate(id, createCustomerDto, {new: true});
return customer;
}
}
And my test
import { Test, TestingModule } from '#nestjs/testing';
import { CustomerController } from './customer.controller';
import {CustomerService} from './customer.service';
import {CreateCustomerDto} from './dto/customer/customer.dto';
import {Customer} from './interfaces/customer.interface';
import {InjectModel} from '#nestjs/mongoose';
jest.mock('./customer.service.ts');
jest.mock('./customer.controller.ts');
jest.mock('./interfaces/customer.interface.ts');
describe('Customer Controller', () => {
let customerController: CustomerController;
let customerService: CustomerService;
beforeAll(async () => {
const module = await Test.createTestingModule({
controllers: [CustomerController],
providers: [CustomerService],
}).compile();
customerController = module.get(CustomerController);
customerService = module.get<CustomerService>(CustomerService);
});
describe('Create Post', () => {
const dto = new CreateCustomerDto();
const res = 200;
const customerModel: Customer;
it('should return an collection of customer when created ', async () => {
const expectedResult = new CustomerService(#InjectModel(customerModel<Customer>));
jest.spyOn(customerService, 'createCustomer').mockResolvedValue(customerModel);
expect(await customerController.createPost(res, dto)).toBe(expectedResult);
});
});
});
**Any thoughts ?**
As already mentioned in the comments, make sure you have your jest properly configured. Try to add/modify the following to your package.json
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}

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

redux saga put not dispatching action

I'm trying to to do integration tests, by mounting a smart connected component.
The fetch action that is within componentDidMount of my smart components dispatches just fine, and it's taken by my Saga. Although it is supposed to put a success action, it doesn't .
Here is my testing code :
import React from 'react'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import MockAdapter from 'axios-mock-adapter'
import Enzyme,{ mount,render } from 'enzyme'
import Tasks from '../containers/tasks.jsx'
import createSagaMiddleware from 'redux-saga'
import axios from "axios";
import Adapter from 'enzyme-adapter-react-16';
import mySaga from '../actions/tasksSaga'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import reducer from '../reducers'
Enzyme.configure({ adapter: new Adapter() });
describe('App', () => {
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
const mock = new MockAdapter(axios)
const state = {
tasksReducer:{
tasks:[]
},
uiReducer :{
}
};
const todos = [
{
id: 1,
title: 'todo1',
description: 'nice'
},
{
id: 2,
title: 'todo2',
description: 'nice'
}
]
beforeAll(() => {
mock.onGet('http://localhost:9001/tasks').reply(200, {tasks:todos})
})
it('renders an App container', () => {
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware),
)
sagaMiddleware.run(mySaga)
const wrapper = mount(
<Provider store={store}>
<Tasks />
</Provider>
)
wrapper.instance().forceUpdate()
expect(wrapper.find('Task')).toHaveLength(3)
})
})
My success action is never called although data is good.

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

Redux: How to test a connected component?

I am using Enzyme to unit test my React components. I understand that in order to test the raw unconnected component I'd have to just export it and test it (I've done that). I have managed to write a test for the connected component but I am really not sure if this's the right way and also what exactly would I want to test for the connected component.
Container.jsx
import {connect} from 'react-redux';
import Login from './Login.jsx';
import * as loginActions from './login.actions';
const mapStateToProps = state => ({
auth: state.auth
});
const mapDispatchToProps = dispatch => ({
loginUser: credentials => dispatch(loginActions.loginUser(credentials))
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Container.test.js
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
describe('Container Login', () => {
it('should render the container component', () => {
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
auth: {
sport: 'BASKETBALL'
}
});
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
});
This is an interesting question.
I usually do import both container and component to do the testing. For container testing I use, redux-mock-store. Component testing is for testing async functions. For instance in your case, login process is an async function using sinon stubs. Here is a snippet of the same,
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { stub } from 'sinon';
const mockStore = configureMockStore([thunk]);
describe('Container Login', () => {
let store;
beforeEach(() => {
store = mockStore({
auth: {
sport: 'BASKETBALL',
},
});
});
it('should render the container component', () => {
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
it('should perform login', () => {
const loginStub = stub().withArgs({
username: 'abcd',
password: '1234',
});
const wrapper = mount(<Login
loginUser={loginStub}
/>);
wrapper.find('button').simulate('click');
expect(loginStub.callCount).to.equal(1);
});
});
As you pointed out, the way I usually do this is to export the un-connected component as well, and test that.
i.e.
export {Login};
Here's an example. Source of the component, and source of the tests.
For the wrapped component, I don't author tests for those because my mappings (mapStateToProps and mapDispatchToProps) are generally very simple. If I wanted to test a wrapped component, I'd really just be testing those maps. So those are what I would choose to explicitly test, rather than re-testing the entire component in a wrapped form.
There are two ways to test those functions. One way would be to export the functions within the module itself.
i.e.;
export {mapStateToProps, mapDispatchToProps}
I'm not a huge fan of this, because I wouldn't want other modules in the app to access them. In my tests, I sometimes use babel-plugin-rewire to access "in-scope" variables, so that's what I would do in this situation.
That might look something like:
import {
Login, __Rewire__
}
const mapStateToProps = __Rewire__.__get__('mapStateToProps');
describe('mapStateToProps', () => { ... });
If we have a router issue, we can consider to add the router lib into the test file, eg:
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { mount } from 'enzyme';
import ReadDots from './ReadDots';
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
dot: {
dots: [
{
id: '1',
dot: 'test data',
cost: '100',
tag: 'pocket money'
}
]
}
});
describe('<ReadDots />', () => {
it('should render ReadDots component', () => {
const component = mount(
<Provider store={store}>
<Router>
<ReadDots />
</Router>
</Provider>
);
expect(component.length).toEqual(1);
});
});