mocking api call in vuex action - unit-testing

I have a vuex store with an action.
//actions.js
import api from '#/api.js'
export default {
getAllProducts ({commit}) {
// call the api and return a promise with the products
api.fetchAllProducts().then((products) => {
commit('getAllProducts', products)
})
}
Now to test it!
// actions.spec.js
import actions from './actions.js'
import api from './api.js'
describe('shop actions', () => {
it('calls api and fetches products', () => {
let state = {items: []}
let commit = sinon.spy()
let stub = sinon.stub(api, 'fetchAllProducts')
stub.resolves('a product')
actions.getAllProducts({commit, state})
expect(commit.args).to.deep.equal([
['SET_ALL_PRODUCTS', 'a product']
])
})
})
This is my attempt so far. It doesn't work for a few reasons.
The sinon stub on the api function will not also stub the imported api on actions.js.
The api function returns a promise, so the test instead of waiting for it to resolve just returns the assertion, so commit.args will always be [ ]
Any advice on how I could test the vuex action. I think the main difficulty is in stubbing the api module, and I'm quite stuck. Any advice is appreciated :)

The action getAllProducts should return the promise.
getAllProducts ({ commit }) {
return api.fetchAllProducts().then((products) => {
commit('SET_ALL_PRODUCTS', products)
})
}
Then your test code should be like this:
describe('shop actions', () => {
it('calls api and fetches products', done => {
let state = {items: []}
let commit = sinon.spy()
let stub = sinon.stub(api, 'fetchAllProducts')
stub.resolves('a product')
actions.getAllProducts({commit, state}).then(() => {
expect(commit.args).to.deep.equal([
['SET_ALL_PRODUCTS', 'a product']
])
done()
})
stub.restore()
})
})
Also, we can use async/await if you are not returning promise from the action.
it('calls api and fetches products', async () => {
let state = {items: []}
let commit = sinon.spy()
let stub = sinon.stub(api, 'fetchAllProducts')
stub.resolves('a product')
await actions.getAllProducts({commit, state})
expect(commit.args).to.deep.equal([
['SET_ALL_PRODUCTS', 'a product']
])
stub.restore()
})

Related

vitest failed to mock quasar

I am having vue3 app with vite and vitest and trying to mock the Quasar useQuasar composable which I am using in my custom Composable like:
// useLoginRequestBuilder.ts
import { makeUserAuthentication } from "#/main/factories"
import { useQuasar } from "quasar"
export function useLoginRequestBuilder() {
const $q = useQuasar()
async function login() {
try {
$q.loading.show()
const auth = makeUserAuthentication()
return await auth.signinRedirect()
} catch (e) {
console.log(e)
$q.loading.hide()
$q.notify({
color: "red-4",
textColor: "white",
icon: "o_warning",
message: "Login Failed!",
})
}
}
return {
login,
}
}
and I am trying to mock quasar in tests like:
// useLoginRequestBuilder.spec.ts
import { useLoginRequestBuilder } from "#/main/builders"
vi.mock("quasar", () => ({ // <--- this is not really mocking quasar
useQuasar: () => ({
loading: {
show: () => true,
hide: () => true,
},
}),
}))
const spyAuth = vi.fn(() => Promise.resolve(true))
vi.mock("#/main/factories", () => ({
makeUserAuthentication: () => ({
signinRedirect: () => spyAuth(),
}),
}))
describe("test useLoginRequestBuilder", () => {
test("should call signinRedirect", async () => {
const { login } = useLoginRequestBuilder()
const sut = await login()
expect(sut).toBe(true)
})
})
vi.mock("quasar"... is failing to mock quasar and I am getting below error. That means, it failed to mock and failed to get the $q.loading.... object.
TypeError: Cannot read properties of undefined (reading 'loading')
I understand that there is a separate testing lib for quasar, here but I think this is not really the case here.
Bordering on a necro-post, but I had a similar issue that the mocking factory wasn't creating the plugins being used in non-Vue components, and had to mock each call individually in the end.
Though I'd add it here for anyone else
vitest.mock("quasar", () => vi.fn()); // this doesn't mock out calls
// use individual mocks as below
import { Loading } from "quasar";
vi.spyOn(Loading, "show").mockImplementation(() => vi.fn());
vi.spyOn(Loading, "hide").mockImplementation(() => vi.fn());

Mocking vuex action using and Mocha

I'm currently testing vuex module specifically actions.
Here's my code:
store/modules/users.js
export const state = () => ({
users: [],
})
export const mutations = () => ({
SET_USERS(state, users) {
console.log('Should reach Here');
state.users = users
}
})
export const actions = () => ({
getUsers({ commit }) {
return axios.get('/users')
.then(response => {
console.log('Reaching Here');
commit('SET_USERS', response.data.data.results)
})
.catch(error => {
console.log(error);
})
}
})
export const getters = () => {
users(state) {
return state.users;
}
};
Then when I test my actions:
tests/store/modules/users.js
it('should dispatch getUsers', () => {
mock.onGet('/users').reply(200, {
data: {
results: [
{ uid: 1, name: 'John Doe' },
{ uid: 2, name: 'Sam Smith' }
]
},
status: {
code: 200,
errorDetail: "",
message: "OK"
}
});
const commit = sinon.spy();
const state = {};
actions.getUsers({ commit, state });
expect(getters.users(state)).to.have.lengthOf(2);
});
when I try to run the test npm run dev it shows the console.log from action but from mutation SET_USERS it doesn't show the console.log
I'm referring to this documentation which I can use spy using sinon()
https://vuex.vuejs.org/guide/testing.html
How can I access the commit inside action to call mutation SET_USERS?
According to sinon docs
A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test.
const commit = sinon.spy();
That is not the 'commit' from Vuex, you should test your mutation individually
actions.getUsers({ commit, state });
The commit argument is actually the spy, it will never trigger the mutation.
To test your mutation it could be something like this
mutations.SET_USERS(state, mockedUsers)
expect(state).to.have.lengthOf(mockedUsers.length)
...

vue.js unit test w sinon, how to stub a method?

I would like to stub this method ( this.login(°. ) in my submit method of a component, but I get an error :
✗ calls store action login when the form is submitted
TypeError: Cannot stub non-existent own property login
here is the method :
methods: _.extend({}, mapActions(['login']), {
clearErrorMessage () {
this.hasError = false
},
submit () {
return this.login({user: { email: this.email, password: this.password }})
.then((logged) => {
if (logged) {
this.$router.push('shoppinglists')
} else {
this.hasError = true
}
})
}
}),
I tried the following
sandbox.stub(LoginPage, 'login').withArgs(payload).returns(Promise.resolve(response))
in my spec
describe('LoginPage.vue', () => {
let actions
let getters
let store
var sandbox, payload, response
beforeEach(() => {
sandbox = sinon.sandbox.create()
...
})
afterEach(() => {
sandbox.restore()
})
it('calls store action login when the form is submitted', () => {
payload = {user: {email: 'john.doe#domain.com', password: 'john123'}}
response = true
sandbox.stub(LoginPage, 'login').withArgs(payload).returns(Promise.resolve(response))
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
expect(actions.login.calledOnce).to.equal(true)
})
just stubbing the action with a Promise resolving the status ( true / false
import LoginPage from '#/pages/LoginPage'
import Vue from 'vue'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('LoginPage.vue', () => {
let actions
let store
let sandbox
beforeEach(() => {
sandbox = sinon.sandbox.create()
})
afterEach(() => {
sandbox.restore()
})
it('calls store action login when the form is submitted, w good credentials', (done) => {
actions = {
login: sandbox.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
actions
})
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
expect(wrapper.data().hasError).to.equal(false)
done()
})
})
it('calls store action login when the form is submitted, w wrong credentials', (done) => {
actions = {
login: sandbox.stub().returns(Promise.resolve(false))
}
store = new Vuex.Store({
actions
})
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
expect(wrapper.data().hasError).to.equal(true)
done()
})
})
})

Mocha - how to check if element exist after ajax call

How to check if select have some option in it after it has been updated by ajax function. My select component is like below
class Select extends React.component(){
constructor(){
super()
this.state.item=[]
}
componentWillMount(){
axios.get(list)
.then(function (response) {
this.setState({item:response.data})
// reponse data ['america','singapore','vietnam']
})
}
render(){
return(
<select>
this.state.item.map((i) => {
<option value="i">i</option>
})
</select>
)
}
}
Here is my try:
import {expect} from 'chai'
import {shallow} from 'enzyme'
import sinon from 'sinon'
import Select from 'Select'
describe('select list', () => {
it('should have options', () => {
const wrapper = shallow(<Select />)
wrapper.update()
const actual = wrapper.find('option').length
expect(actual).to.not.equal(0)
})
})
What wrong with this is, I got var actual = 0. It supposes to be 3. So I guess I missing something with axios. What should I add to my spec?
Your GET request might be still waiting for the response but mocha already has completed the test execution.
You could add a timeout and assert after a while and then call the done() callback when you are done with the test. Please take a look at the mocha's asynchronous code section.
describe('select list', () => {
it('should have options', (done) => {
const wrapper = shallow(<Select />)
wrapper.update()
setTimeout(() => {
const actual = wrapper.find('option').length
expect(actual).to.not.equal(0)
done();
}, 2000)
})
})
I recommend you to check the axios-mock-adapter which allows you to mock request rather than sending an actual request to the server.

How do you test Collection.allow( ) functions that rely on the user ID?

Given the following collection and access control defintion
class TasksCollection extends Mongo.Collection {
insert (task, callback) {
const doc = _.extend({}, task, {
createdOn: new Date(),
owner: this.userId
})
super.insert(doc, callback)
}
}
export const Tasks = new TasksCollection('tasks')
// Simple checks to ensure that the user is logged in before making changes.
Tasks.allow({
insert: (userId, doc) =>=> !!userId,
update: (userId, doc, fields, modifier) => !!userId,
remove: (userId, doc) => !!userId
})
How would you test to ensure that it works using Mocha/Chai/Sinon? This is what I have tried.
import { Meteor } from 'meteor/meteor'
import { resetDatabase } from 'meteor/xolvio:cleaner';
import { assert, expect } from 'chai'
import { Tasks } from '/imports/api/tasks'
import sinon from 'sinon'
describe('collection test', () => {
beforeEach(() => {
resetDatabase()
})
it('can see a collection', () => {
assert(Tasks, 'unable to see sample collection')
})
it('can query an empty collection', () => {
expect(Tasks.find({}).fetch()).to.be.empty
})
it('fails to add to a collection when the user is not logged in', (done) => {
expect(Tasks.find({}).fetch()).to.be.empty
Tasks.insert({
text: 'hello world'
}, (error) => {
console.log('expected', error) // this is also a 404
assert(error)
done()
})
})
describe('logged in', () => {
let sandbox
beforeEach(() => {
sandbox = sinon.sandbox.create()
sandbox.stub(Meteor, 'userId').returns(42)
})
afterEach(() => {
sandbox.restore()
})
it('can add to a collection', (done) => {
expect(Tasks.find({}).fetch()).to.be.empty
Tasks.insert({
text: 'hello world'
}, (error, _id) => {
console.log(error)
assert(!error)
const results = Tasks.find({}).fetch()
expect(results).to.have.lengthOf(1)
expect(results[0].defaultValue).to.equal(42)
expect(results[0]._id).to.equal(_id)
expect(results[0].createdOn).to.not.be.undefined
done()
})
})
})
})
UPDATE: But I get a 404 error when calling the server.
The insecure package is already removed.
UPDATE: I am only testing on the client for now as the authorization can only be done from a client call.