Testing vuex mocked mutation with jest - unit-testing

I have been testing my vuex store with the following structure
/src/store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
data: []
};
export const mutations = {
SET_DATA(state, data) {
state.data = data;
}
};
export const actions = {
fetchData({ commit }) {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(data => commit(SET_DATA, data))
.catch(err => console.log(err));
}
};
export default new Vuex.Store({
state,
mutations,
actions
});
My test file for actions asserts for the url and commit triggering
it("should fetch correct data and commit to store", () => {
// Mock fetch
global.fetch = jest.fn().mockImplementationOnce(() =>
Promise.resolve({
status: 200,
json: () => Promise.resolve(JSON.stringify(mockData))
})
);
return store.dispatch("fetchData").then(() => {
expect(global.fetch).toHaveBeenCalledWith(
"https://jsonplaceholder.typicode.com/users"
);
expect(mockSetData).toHaveBeenCalled();
});
});
Currently my test passes only for URL and does not call mockSetData which is a jest mocked function.
I'm new to TDD and not quite sure why this fails. Here's an implementation on codesandbox for more context

Related

React & React Native Testing Library wait for async state update caused by useEffect on mount

I can't seem to get this simple test to work in react-testing-library & react-native-testing-library. I've tried various combinations of wrapping the render function in act, or using waitFor and other async utils, but the test never waits for the component to re-render after useEffect causes the async api call to set the new state.
Also worth noting I receive the warning: An update to TestComponent inside a test was not wrapped in act(...).`. I'm aware of this issue but no method that I've seen solved it for me.
import React, { useEffect, useState } from 'react'
import { View, Text } from 'react-native'
import { render, waitFor } from 'test-utils'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import { useApi } from './index'
const server = setupServer(
rest.get('http://localhost/MOCK_VAR/some-endpoint', (req, res, ctx) => {
return res(ctx.json({ greeting: 'hello there' }))
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
function TestComponent() {
const { apiRequest } = useApi()
const [result, setResult] = useState(null)
useEffect(() => {
makeApiCall()
})
const makeApiCall = async () => {
const apiResult = await apiRequest({ url: '/some-endpoint' })
console.log(apiResult.greeting) // <-- 'hello there'
setResult(apiResult.greeting)
}
return (
<View>
<Text>{result}</Text>
</View>
)
}
describe('Test useApi hook', () => {
test('test post request', async () => {
const { findByText } = render(<TestComponent />)
const greeting = await findByText('hello there')
await waitFor(() => { // <-- never waits
expect(greeting).toBeTruthy()
})
})
})
My issue was awaiting the findBy function. From the docs it says findBy* methods have waitFor already built in. So simply removing the await solved the issue.
What worked for me:
test('test post request', async () => {
const { findByText } = render(<TestComponent />)
const greeting = findByText('hello there')
waitFor(() => expect(greeting).toBeTruthy())
})

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)
...

Jest: How to properly test void functions that include promises?

I'm writing an app with React Native. I use Firebase Cloud Messaging for real time communication. I'm currently writing the unit tests for the FCM code using jest. The problem is that I'm struggling to make it work, since it consists of void functions that contain promises. Let me give you the code:
fcm.js:
import { Alert } from "react-native";
import firebase from "react-native-firebase";
export const checkNotificationsPermission = () => {
firebase
.messaging()
.hasPermission()
.then(enabled => {
if (enabled) {
// User has permissions.
} else {
// User doesn't have permission.
Alert.alert(
alertMessages.firebasePrepareForPermissionTitle,
alertMessages.firebasePrepareForPermissionMessage,
[{ text: buttonTexts.ok, onPress: () => requestNotificationsPermission() }]
);
}
});
};
export const requestNotificationsPermission = () => {
firebase
.messaging()
.requestPermission()
.then(() => {
// User has authorised.
})
.catch(() => {
// User has rejected permissions.
Alert.alert(
alertMessages.firebasePrepareForPermissionTitle,
alertMessages.firebasePermissionDeniedMessage,
[{ text: buttonTexts.ok, onPress: () => {} }]
);
});
};
fcm.test.js:
import firebase from "react-native-firebase";
describe("checkNotificationsPermission", () => {
beforeEach(() => {
return checkNotificationsPermission();
});
afterEach(() => {
jest.clearAllMocks();
});
it("should call firebase's hasPermission", async () => {
expect(firebase.messaging().requestPermission).toHaveBeenCalledTimes(1);
});
});
Here is how I mocked firebase (__mocks__/react-native-firebase.js):
const firebase = {
messaging: jest.fn(() => ({
hasPermission: jest.fn(() => new Promise(resolve => resolve(true))),
requestPermission: jest.fn(() => new Promise(resolve => resolve(true)))
}))
};
export default firebase;
The test fails with Expected mock function to have been called one time, but it was called zero times..Since this wouldn't work and I had a similar question about promises which got answered I tried to apply what I learned there which resulted in the following code.
fcm.js:
import { Alert } from "react-native";
import firebase from "react-native-firebase";
export const checkNotificationsPermission = () =>
new Promise((resolve, reject) => {
firebase
.messaging()
.hasPermission()
.then(enabled => {
if (enabled) {
// User has permissions.
resolve(true);
} else {
// User doesn't have permission.
Alert.alert(
alertMessages.firebasePrepareForPermissionTitle,
alertMessages.firebasePrepareForPermissionMessage,
[
{
text: buttonTexts.ok,
onPress: () =>
requestNotificationsPermission()
.then(() => resolve(true))
.catch(() => reject(false))
}
]
);
}
});
});
export const requestNotificationsPermission = () =>
new Promise((resolve, reject) => {
firebase
.messaging()
.requestPermission()
.then(() => {
// User has authorised.
resolve(true);
})
.catch(() => {
// User has rejected permissions.
reject(true);
Alert.alert(
alertMessages.firebasePrepareForPermissionTitle,
alertMessages.firebasePermissionDeniedMessage,
[{ text: buttonTexts.ok, onPress: () => {} }]
);
});
});
fcm.test.js:
import firebase from "react-native-firebase";
import { requestNotifcationsPermission } from "./fcm";
describe("checkNotificationsPermission", () => {
afterEach(() => {
jest.clearAllMocks();
});
it("should call firebase's hasPermission", () => {
expect.assertions(1);
return checkNotificationsPermission().then(() => {
expect(firebase.messaging().requestPermission).toHaveBeenCalledTimes(1);
});
});
});
But for some reason these tests still fail. I empirically tested and ensured the code works. Just the unit tests won't pass.
Edit
I accidentally left out that both fcm.js also have the following imports:
import alertMessages from "../../config/constants/alertMessages";
import buttonTexts from "../../config/constants/buttonTexts";

Axios Mock Adapter waiting for timeout before dispatching?

In my React Redux app, I tried to do some tests to one of my function that has settimeout in it
// MyFunc.js
export function updateSomething(id, data) {
return (dispatch, getState) => {
dispatch({type: 'dispatch1'})
settimeout(() => {
axios.get('/data')
.then((res) => {
dispatch({type: 'timeoutted_dispatch', data: res.data})
})
.catch(error => {
dispatch({type: 'dispatch_error'});
})
},3000);
dispatch({type: 'dispatch_end'});
}
}
// MyTest.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';;
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Lets do some tests', () => {
it('Should dispatch timeoutted function', () => {
const store = mockStore({});
// Get all the dispatches for testing purpose
store.dispatch(updateSomething(1, {test: 'test'}))
.then(() => {
const actionList = store.getAction();
expect(actionList).toEqual(allDispatches);
// At this point, I won't be able to get anything inside timeout function
})
});
})
How do I set some kind of Await so I can wait for the timeout before trying to retrieve the data ?

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.