I have a namespaced Vuex module that I am attempting to test using Jest, after making the mutation to my mocked state it does not appear to change.
Here is my addEvents mutation
addEvents: (state, infos) => {
try {
state.events = state.events.concat(infos);
} catch (error) {
console.error("[ERR vuex event set] ", e.message);
}
event.test.js
import { createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
//store
import event from '../index.js'
const localVue = createLocalVue();
localVue.use(Vuex);
describe('event', () => {
//intial state
const state = {
events: []
}
const newEvent = {
text: 'Hello',
eventCode: 'importantEvent'
}
//mutate
event.commit('event/addEvents', newEvent);
expect(state.events).toContainEqual(newEvent)
})
})
I am importing from my store index which has many modules in it. I think I am referencing my desired store properly. When I tried to import the specific module I wanted to test my test was unable to find my mutations.
My jest output returns
FAIL vue/vuex/modules/event.test.js
● event › encountered a declaration exception
expect(received).toContainEqual(expected) // deep equality
Expected value: {"eventCode": "importantEvent", "text": "Hello"}
Received array: []
20 | //mutate
21 | event.commit('event/addEvents', newEvent);
> 22 | expect(state.events).toContainEqual(newEvent)
| ^
23 | })
at Suite.<anonymous> (vue/vuex/modules/event.test.js:22:24)
at Object.<anonymous> (vue/vuex/modules/event.test.js:9:1)
Thanks to Estus Flask for helping me figure out my issue was not referencing store properly. This is the code I have that is working including my reset route which already existed. Renamed my event import to store for clarity.
import { createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
//store
import store from '../index.js'
const localVue = createLocalVue();
localVue.use(Vuex);
describe('event mutations', () => {
beforeEach(() => {
//resets state beforeEach
store.commit("event/reset");
});
it('adding events works', () => {
const newEvent = {
text: 'Hello',
eventCode: 'importantEvent'
};
//mutate
store.commit('event/addEvents', newEvent);
expect(store.state.event.events).toContainEqual(newEvent);
});
it('store resets properly between tests and reset works', () => {
expect(store.state.event.events).toHaveLength(0);
});
})
Related
I recently implemented Jest with Vue-test-utils to test the Vue components of a rather large existing project. This is a vue 2 project using babel. After a long time of setup i finally started to write my first tests.
I am currently encountering two major issues :
shallowMount function does not work as expected because child components are rendered
my tests have trouble handling localized imports in the tested components.
Here is my code that test the auth of my app.
login.spec.js
import { shallowMount, createLocalVue } from "#vue/test-utils";
import Vuex from "vuex";
import Login from "../src/views/user/Login.vue";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Login", () => {
let actions;
let store;
let state;
let wrapper;
beforeEach(() => {
actions = {
signIn: jest.fn(),
};
//mock store auth module
let auth = {
namespaced: true,
state: {},
actions,
};
store = new Vuex.Store({
modules: {
auth,
},
});
wrapper = shallowMount(Login, { store, localVue });
});
test("should dispatch a store action with user credentials when form is submitted", async () => {
// Setup
//Set login data
const email = "test#example.com";
const password = "password123";
//mount component with data
wrapper.setData({ form: { email: email, password: password } });
//Action
const form = wrapper.find("b-form");
await form.trigger("submit.prevent");
//Assertion
expect(actions.signIn).toHaveBeenCalled();
});
});
For my login workflow i use a store subscriber that listen mutations.
subscriber.js
import store from "./index";
import axios from "axios";
store.subscribe((mutation) => {
//do things
}
This subscriber is executed in my project main.js file :
require("./store/subscriber");
When i run my tests this error occurred :
TypeError: Cannot read property 'subscribe' of undefined
2 | import axios from "axios";
3 |
> 4 | store.subscribe((mutation) => {
the wrapper renders child components of my Login Component even if I use shallowMount()
why store is undefined ? it is imported line 1 in subscriber.js file.
I am new to writing test cases for react. What am I doing wrong in the below code?
My component
// Dummy.js
import React, {Component} from "react";
import axios from "axios";
export default class Dummy extends Component {
state = {
name: "",
error: false,
data : []
};
componentDidMount() {
this.getData();
}
getData = () => {
axios
.get("https://reqres.in/api/users?page=2")
.then((response) => {
this.setState({
name : response.data.data[0].first_name,
data : response.data.data
});
return "Success"
})
.catch(() => {
this.setState({
error: true,
});
});
};
render() {
return <div>
<h1 data-testid ="test">{this.state.name}</h1>
</div>;
}
}
My test case
// dummy.test.js
import React from "react";
import {shallow} from "enzyme";
import Dummy from "../Dummy";
import axios from "axios";
jest.mock("axios");
const data = {
page: 2,
per_page: 6,
total: 12,
total_pages: 2,
data: [
{
id: 7,
email: "michael.lawson#reqres.in",
first_name: "Michael",
last_name: "Lawson",
avatar: "https://reqres.in/img/faces/7-image.jpg",
},
{
id: 8,
email: "lindsay.ferguson#reqres.in",
first_name: "Lindsay",
last_name: "Ferguson",
avatar: "https://reqres.in/img/faces/8-image.jpg",
},
],
};
test("should fetch users", () => {
const wrapper = shallow(<Dummy />);
const resp = {data: data};
axios.get.mockResolvedValue(resp);
wrapper
.instance()
.getData()
.then((resp) => {
console.log(resp);
expect(wrapper.state("data")).toEqual(resp);
});
});
Below is the error I am getting when trying to execute my test case.
FAIL src/Dummy/__tests__/dummy.test.js
× should fetch users (5 ms)
● should fetch users
TypeError: Cannot read property 'then' of undefined
12 | }
13 | getData = () => {
> 14 | axios
| ^
15 | .get("https://reqres.in/api/users?page=2")
16 | .then((response) => {
17 | this.setState({
at Dummy.getData (src/Dummy/Dummy.js:14:5)
at Dummy.componentDidMount (src/Dummy/Dummy.js:11:10)
at fn (node_modules/enzyme/src/ShallowWrapper.js:429:22)
at Object.batchedUpdates (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:807:16)
at new ShallowWrapper (node_modules/enzyme/src/ShallowWrapper.js:428:26)
at shallow (node_modules/enzyme/src/shallow.js:10:10)
at Object.<anonymous> (src/Dummy/__tests__/dummy.test.js:32:19)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 3.862 s
Ran all test suites related to changed files.
Don't know much about jest and enzyme. Please suggest functions/code which helps me on solving my problem.
mockResolvedValue is a Jest function that should be called like so:
axios.get = jest.fn().mockResolvedValue(mockedUsers);
The rest of the test should be unchanged.
When you directly mock any module or library, it's exports are replaced with undefined values. To be able to correctly mock axios and make it useful in your test-case, take a look at this package: https://www.npmjs.com/package/axios-mock-adapter
Also, depends on the enzyme version, in newest versions shallow is triggering componentDidMount lifecycle, so you need to mock the HTTP client before shallow-rendering - https://enzymejs.github.io/enzyme/docs/api/shallow.html
Please follow below steps:
Create the mockaxios.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} }))
};
Inside your dummy.test.js mock it with jest
import axios from 'axios'
import mockAxios from './mockaxios.js'
jest.mock('axios', () => ({
__esModule: true,
default: mockAxios
}));
And finally use it as
mockAxios.get.mockImplementationOnce(() => Promise.resolve(resp));
Originally, I was using new CounterStore inside React.createContext()
context.ts
import React from 'react'
import { stores, PersistState, CounterStore } from '#/store/index'
import type { ICounterStore } from '#/types/index'
export const FrameItContext = React.createContext<ICounterStore>(new CounterStore())
export const useCounterStore = () => React.useContext(FrameItContext)
Then I started using Mobx Persist Store in my app.
persist.ts
import { persistence, StorageAdapter } from 'mobx-persist-store'
import { CounterStore } from '#/store/index'
const read = (name: string): Promise<string> =>
new Promise((resolve) => {
const data = localStorage.getItem(name) || '{}'
console.log('got data: ', data)
resolve(data)
})
const write = (name: string, content: string): Promise<Error | undefined> =>
new Promise((resolve) => {
localStorage.setItem(name, content)
console.log('write data: ', name, content)
resolve(undefined)
})
export const PersistState = persistence({
name: 'CounterStore',
properties: ['counter'],
adapter: new StorageAdapter({ read, write }),
reactionOptions: {
// optional
delay: 2000,
},
})(new CounterStore())
And I changed my code to use PersistState instead of new CounterStore()
context.ts
import React from 'react'
import { stores, PersistState, CounterStore } from '#/store/index'
import type { ICounterStore } from '#/types/index'
export const FrameItContext = React.createContext<ICounterStore>(PersistState)
export const useCounterStore = () => React.useContext(FrameItContext)
It only logs got data: {} to the console. The write function never gets called.
Is there anything I am doing wrong?
Coincidentally, a simple Counter example on Codesandbox works perfectly fine → https://codesandbox.io/s/mobx-persist-store-4l1dm
The example above works on a simple Chrome extension or a web app but just doesn't seem to work with my specific application so I wrote a manual implementation of saving to LocalStorage.
Use toJSON() on the store to keep track of which properties should be saved:
toJSON() {
const { color, counter } = this
return {
color,
counter,
}
}
And add the localStorage logic just below the constructor(). First, check if the localStorage contains latest value & return it, if it does.
If there is nothing saved, then save it inside localStorage.
constructor() {
...
const name = "CounterStore"
const storedJson = localStorage.getItem(name)
if (storedJson) Object.assign(this, JSON.parse(storedJson))
autorun(() => {
localStorage.setItem(name, JSON.stringify(this))
})
}
Codesandbox → https://codesandbox.io/s/mobx-persist-store-manual-implementation-vm38r?file=/src/store.ts
Currently trying to test a vue component that is using the vuex ...mapState method but just by bringing it into the component fails my tests.
This is the current error i'm getting:
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import _Object$defineProperty from "../../core-js/object/define-property";
This is my component I'm trying to test.
SimpleComponent.vue
<template>
<h1> Hello!!!</h1>
</template>
<script>
import mapState from 'vuex';
export default {
computed: {
...mapState(["users"])
}
}
</script>
<style>
</style>
This is my store but simplified for testing purposes.
store.js
import Vue from "vue";
import Vuex from "vuex";
const fb = require("./firebaseConfig.js");
Vue.use(Vuex);
fb.auth.onAuthStateChanged(user => {
if (user) {
store.commit('setCurrentUser', user)
// realtime updates from our posts collection
fb.usersCollection.onSnapshot(querySnapshot => {
let userArray = []
querySnapshot.forEach(doc => {
let user = doc.data()
user.id = doc.id
userArray.push(user)
})
store.commit('setUsers', userArray)
})
}
})
export const store = new Vuex.Store({
state: {
users:[],
},
actions: {
clearData({ commit }) {
commit('setUsers', null);
},
},
mutations: {
setUsers(state, val){
state.users = val
},
},
});
And here is my test
SimpleComponentTest.spec.js
import { shallowMount, createLocalVue } from '#vue/test-utils';
import SimpleStore from '../../src/components/SimpleComponent.vue';
import Vuex from 'vuex';
const sinon = require('sinon');
const localVue = createLocalVue();
localVue.use(Vuex);
describe('SimpleComponent.vue', () => {
it('Is Vue Instance', () => {
const wp = shallowMount(SimpleComponent, { computed: { users: () => 'someValue' }, localVue });
expect(wp.isVueInstance()).toBe(true);
});
});
Im using Jest with Sinon for testing. I am a little confused on the proper way to set up the store in my test but this is one way I found when looking online.
The code base is a little bigger than what I am showing you but that is because after running into errors for hours I figured I needed the simplest piece of code to test that uses the ...mapState method and build it back up from there.
Any help would be greatly appreciated :)
Goal: To get a simple test to pass that tests a component that uses ...mapState([]) from vuex
Wondering if someone can point out what I expect is a stupid mistake.
I have an action for user login.
I'm trying to test this action, I've followed the redux documentation as well as the redux-mock-store documentation however I keep getting an error as follows:
TypeError: Cannot read property 'default' of undefined
4 | import thunkMiddleware from 'redux-thunk'
5 |
> 6 | const middlewares = [thunkMiddleware] // add your middlewares like `redux-thunk`
| ^
7 | const mockStore = configureStore(middlewares)
8 |
9 | describe("userActions", () => {
at Object.thunkMiddleware (actions/user.actions.spec.js:6:22)
My test code is as follows:
import {userActions} from "./user.actions";
import {userConstants} from "../constants/user.constants";
import configureStore from 'redux-mock-store'
import thunkMiddleware from 'redux-thunk'
const middlewares = [thunkMiddleware] // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares)
describe("userActions", () => {
describe("login", () => {
it(`should dispatch a ${userConstants.LOGIN_REQUEST}`, () =>{
const store = mockStore({});
return store.dispatch(userActions.login("someuser", "somepassword")).then(() => {
expect(store.getState().loggingIn).toBeTruthy();
});
})
})
});
I've double checked both redux-thunk and redux-mock-store are included in my npm dev dependencies as well as deleteing the node_modules directory and reinstalling them all with npm install.
Can anyone see what's going wrong?
Thanks
EDIT:
It seems i'm doing something fundamentally wrong, I've tried to simplify it almost back to a clean slate to find where the problem is introduced.
Even with this test:
import authentication from "./authentication.reducer";
import { userConstants } from "../constants/user.constants";
describe("authentication reducer", () => {
it("is a passing test", () => {
authentication();
expect("").toEqual("");
});
});
Against this:
function authentication(){
return "test";
}
export default authentication
I'm getting an undefined error:
● authentication reducer › is a passing test
TypeError: Cannot read property 'default' of undefined
6 |
7 | it("is a passing test", () => {
> 8 | authentication();
| ^
9 | expect("").toEqual("");
10 | });
at Object.<anonymous> (reducers/authentication.reducer.spec.js:8:9)
yes, according to that error, seems you have a problem with module dependencies. Take a look at your webpack configuration.
Concerning the redux-mock-store, I suggest you to create a helper for future testing needs:
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
export default function(middlewares = [thunk], data = {}) {
const mockedStore = configureStore(middlewares)
return mockedStore(data)
}
and you will include it in your test cases and use like that:
beforeEach(() => {
store = getMockStore()
})
afterEach(() => {
store.clearActions()
})
If you wont test redux with thunk you can use redux-thunk-tester module for it.
Example:
import React from 'react';
import {createStore, applyMiddleware, combineReducers} from 'redux';
import {asyncThunkWithRequest, reducer} from './example';
import ReduxThunkTester from 'redux-thunk-tester';
import thunk from 'redux-thunk';
const createMockStore = () => {
const reduxThunkTester = new ReduxThunkTester();
const store = createStore(
combineReducers({exampleSimple: reducer}),
applyMiddleware(
reduxThunkTester.createReduxThunkHistoryMiddleware(),
thunk
),
);
return {reduxThunkTester, store};
};
describe('Simple example.', () => {
test('Success request.', async () => {
const {store, reduxThunkTester: {getActionHistoryAsync, getActionHistoryStringifyAsync}} = createMockStore();
store.dispatch(asyncThunkWithRequest());
const actionHistory = await getActionHistoryAsync(); // need to wait async thunk (all inner dispatch)
expect(actionHistory).toEqual([
{type: 'TOGGLE_LOADING', payload: true},
{type: 'SOME_BACKEND_REQUEST', payload: 'success response'},
{type: 'TOGGLE_LOADING', payload: false},
]);
expect(store.getState().exampleSimple).toEqual({
loading: false,
result: 'success response'
});
console.log(await getActionHistoryStringifyAsync({withColor: true}));
});
});