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

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

Related

How to mock AWS Amplify in Jest?

I am creating an application with Vue and using Vue Test Utils and Jest as a unit testing framework. However, I encountered an issue in testing the scenario when it should show invalid credentials on failed login. I was wondering how to mock the Auth of AWS Amplify. I am not quite sure if I am doing the testing right because I am new to unit testing in frontend.
Login.vue:
import { Auth } from 'aws-amplify'
import { required } from 'vuelidate/lib/validators'
export default {
name: 'loginComponent',
data() {
return {
form: {
email: null,
password: null,
},
authErrMsg: '',
isShowAuthErr: false,
isLoading: false,
}
},
validations: {
form: {
email: { required },
password: { required }
}
},
methods: {
validateState(name) {
const { $dirty, $error } = this.$v.form[name];
return $dirty ? !$error : null;
},
onSubmit() {
this.$v.$touch();
if (this.$v.$invalid) {
return
}
this.isLoading = true
this.isShowAuthErr = false
const { email, password } = this.form
Auth.signIn(email, password).then(() => {
this.isLoading = false
this.$store.dispatch('ACTION_SET_LOGGEDIN_STATUS', true)
this.$router.push({ name: 'home' })
}).catch(() => {
this.isLoading = false
this.authErrMsg = 'Invalid login credentials'
this.$store.dispatch('ACTION_SET_LOGGEDIN_STATUS', false)
this.isShowAuthErr = true
})
}
}
}
Login.spec.js:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import Login from '#/components/Login'
import BootstrapVue from 'bootstrap-vue'
import Vuelidate from 'vuelidate'
import Vuex from 'vuex'
import Auth from '#aws-amplify/auth'
import flushPromises from 'flush-promises'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
localVue.use(Vuelidate)
localVue.use(Vuex)
localVue.use(Auth)
let wrapper
beforeEach(() => {
wrapper = mount(Login, { localVue,
form: {
email: null,
password: null,
}
})
})
describe('Login', () => {
it('should error message on failed login', async () => {
wrapper.find('input[name="email"]').setValue('email#gmail.com')
wrapper.find('input[name="password"]').setValue('123ABC')
wrapper.find("form").trigger("submit.prevent")
await flushPromises()
Auth.signIn = jest.fn().mockImplementation(() => {
throw new Error('Incorrect username or password.')
});
expect(Auth.signIn()).rejects.toThrow()
})
})
Error I got:
TypeError: Cannot read property 'clientMetadata' of undefined

Testing a trigger click on a button does not work in Vue using Jest

Testing a trigger click on a button does not work in Vue using Jest.
When I try to find the button in the wrapper the test passes, but when I try a trigger click on the same button so a method will be called it does not work.
Here is the vue file snapshot of the button:
<v-btn #click="viewAppointment(appointment)" class="ma-2" dark small color="orange" id="view-appointment" data-viewAppointmentBtn>
<v-icon left>mdi-eye</v-icon>
<span>View</span>
</v-btn>
Here is the js file that contains the simple method call::
viewAppointment(appointment) {
this.appointment = appointment;
this.viewAppointmentDialog = !this.viewAppointmentDialog;
},
Here is the .spec.js file for the test::
import './setup.js';
import CoachAppointmentsRequests from '../dashboard/coach/appointments/requests/overview/Overview.vue';
import {shallowMount, createLocalVue} from "#vue/test-utils";
import Vuex from "vuex";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("CoachAppointmentsRequests", () => {
let wrapper;
let store;
let actions;
let state;
let getters;
const $route = {
path: 'appointment/requests/:application_id',
params: { application_id: 123 }
}
actions = {
GET_USER_APPOINTMENTS: jest.fn()
};
state = {
user_appointments: [ {id:1, date: 'May 20, 2020'} ],
all_user_appointments: [ {id:1, date: 'May 20, 2020'} ],
};
getters = {
user_appointments: state => state.user_appointments,
all_user_appointments: state => state.all_user_appointments
};
store = new Vuex.Store({
actions,
getters,
state,
});
const getUserAppointments = jest.fn(() => {
return new Promise(resolve => {
process.nextTick(() => {
resolve({
data: [
{ id:1, appointee_id:2}
]
})
})
})
});
beforeEach(() => {
wrapper = shallowMount(CoachAppointmentsRequests, {
propsData: {},
mocks: {
$route,
},
stubs: {},
methods: {
getUserAppointments,
},
store,
localVue,
});
});
it('click on the view appointment button calls the viewAppointment method', () => {
const viewAppointment = jest.fn();
wrapper.setMethods({ viewAppointment })
const viewAppBtn = wrapper.find('#view-appointment');
viewAppBtn.trigger('click');
expect(viewAppointment).toBeCalled();
});
});
Please I will appreciate your assistance with this issue.
The click handler isn't called immediately after trigger(), but rather it's called in the next tick. However, trigger() returns a Promise that resolves when the component is updated, so you could await the result of the call, as shown in the docs example:
it('clicked it', async () => {
// ...
await viewAppBtn.trigger('click')
expect(viewAppointment).toBeCalled()
})
I had a similar problem. I've used shallowMount to mount vue component and click on button wasn't working. The solution was to change shallowMount to mount.

Vue Test Utils / Jest - How to test if class method was called within a component method

I have an interesting problem with a unit test of mine. My unit test is written to click on a button inside a component. This button calls a component method which contains an instance of a class Service (a wrapper class for axios). The only thing this component method does is call Service.requestPasswordReset(). My unit test needs to verify that Service.requestPasswordReset was called.
I know I'm mocking my Service class correctly, because this passes in my unit test:
await Service.requestPasswordReset()
expect(Service.requestPasswordReset).toHaveBeenCalled()
And I know that I'm calling the method correctly on click because this passes in my unit test:
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
I just can't get my test to register that the Service method gets called. Any ideas?
Component
<template lang="pug">
Layout
section
header( class="text-center py-4 pb-12")
h1( class="text-grey-boulder font-light mb-4") Recovery Email
p( class="text-orange-yellow") A recovery email has been sent to your email address
div( class="text-center")
div( class="mb-6")
button(
type="button"
#click.stop="resend()"
class="bg-orange-coral font-bold text-white py-3 px-8 rounded-full w-48"
) Resend Email
</template>
<script>
import Layout from '#/layouts/MyLayout'
import Service from '#/someDir/Service'
export default {
name: 'RecoveryEmailSent',
page: {
title: 'Recovery Email Sent',
},
components: {
Layout,
},
data() {
return {
errorMessage: null
}
},
computed: {
userEmail() {
const reg = this.$store.getters['registration']
return reg ? reg.email : null
},
},
methods: {
async resend() {
try {
await Service.requestPasswordReset({
email: this.userEmail,
})
} catch (error) {
this.errorMessage = error
}
},
},
}
</script>
Service.js
import client from '#/clientDir/BaseClient'
class Service {
constructor() {
this.client = client(baseUrl)
}
requestPasswordReset(request) {
return this.client.post('/account_management/request_password_reset', request)
}
}
export { Service }
export default new Service()
Service.js in __mock__
export default {
requestPasswordReset: jest.fn(request => {
return new Promise((resolve, reject) =>
resolve({
data: {
statusCode: 'Success',
},
})
)
})
}
Unit Test
jest.mock('#/someDir/Service')
import { shallowMount, mount, createLocalVue } from '#vue/test-utils'
import RecoveryEmailSent from './AccountManagement.RecoveryEmailSent'
import Service from '#/someDir/Service'
const localVue = createLocalVue()
// localVue.use(Service) // <-- Tried this, didn't work
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const mockMethods = {
resend: jest.fn()
}
const email = 'testemail#test.com'
const wrapper = mount(RecoveryEmailSent, {
localVue,
computed: {
userEmail() {
return email
},
},
methods: mockMethods
})
// await Service.requestPasswordReset()
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(Service.requestPasswordReset).toHaveBeenCalled()
})
})
I figured it out. Apparently, Jest's .toHaveBeenCalled() doesn't return true if the method in question was called with parameters. You MUST use .toHaveBeenCalledWith(). I don't see anything about this caveat in their docs, but it does seem to be the case.
Here is my passing test code
it('should resend email hash', async () => {
const email = 'testemail#test.com'
const wrapper = mount(AccountManagementForgottenPasswordSubmitted, {
localVue,
computed: {
userEmail() {
return email
},
},
})
await wrapper.find('button').trigger('click')
expect(Service.requestPasswordReset).toHaveBeenCalledWith({
email: email
})
})
You can use inject-loader to mock your Service
Basic idea:
const RecoveryEmailSentInjector = require('!!vue-loader?inject!./AccountManagement.RecoveryEmailSent')
import Service from '#/someDir/Service'
const mockedServices = {
'#/someDir/Service': Service
}
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const RecoveryEmailSentWithMocks = RecoveryEmailSentInjector(mockedServices)
const wrapper = mount(RecoveryEmailSentWithMocks, {
...
})
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(mockedServices.requestPasswordReset).toHaveBeenCalled()
})
})

mock axios request jest network error

I am trying to create async tests with axios-mock and jest.
This is my test file:
var axios = require('axios');
var MockAdapter = require('axios-mock-adapter');
const middlewares = [thunk,axiosMiddleware]
const mockStore = configureMockStore(middlewares)
describe('async-actions', () => {
var instance;
var mock;
beforeEach(function() {
instance = axios.create();
mock = new MockAdapter(instance);
});
afterEach(() => {
mock.reset()
mock.restore()
})
it('creates FETCH_BOOKINGS_SUCCESS when fetch bookings has been done', () => {
mock
.onGet('/bookings').reply(200, {
data: [
{ id: 1, name: 'test booking' }
]
});
const expectedActions = [
{type: "FETCH_BOOKINGS_START" },
{type: "FETCH_BOOKINGS_SUCCESS", }
]
const store = mockStore({
session: {
token: {
token: "test_token"
}
}
})
return store.dispatch(actions.fetchBookingsTest())
.then(
() => {
expect(store.getActions()).toEqual(expectedActions)
})
// return of async actions
})
})
And my action:
export function fetchBookingsTest() {
return (dispatch) => {
dispatch(async.fetchDataStart(namedType));
return dispatch(rest.get(BOOKINGS))
.then(
(data) => {
dispatch(async.fetchDataSuccess(data,namedType));
},
(error) => {
dispatch(async.fetchDataFailure(error,namedType));
}
)
}
}
I have middleware setup that uses the authentication token from the redux store for each get request. That is why I have setup "test_token" in the mock store.
When I run this test I receive the response
[{"type": "FETCH_BOOKINGS_START"}, {"payload": [Error: Network Error], "type": "FETCH_BOOKINGS_FAILURE"}]
Why am I getting a network error? Do i need to do more setup with Jest to avoid authentication with mock-axios?

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.