I want to unit test my vue components. Since I'm working with firebase this is a little bit difficult.
Fir of all, I created a __mocks__ folder to contain all my mocked functions. Inside that folder, I've created firebase.js:
import * as firebase from 'firebase';
const onAuthStateChanged = jest.fn();
const getRedirectResult = jest.fn(() => Promise.resolve({
user: {
displayName: 'redirectResultTestDisplayName',
email: 'redirectTest#test.com',
emailVerified: true,
},
}));
const sendEmailVerification = jest.fn(() => Promise.resolve('result of sendEmailVerification'));
const sendPasswordResetEmail = jest.fn(() => Promise.resolve());
const createUserWithEmailAndPassword = jest.fn(() => {
console.log('heeeeelllo');
Promise.resolve({
user: {
displayName: 'redirectResultTestDisplayName',
email: 'redirectTest#test.com',
emailVerified: true,
},
});
});
const signInWithEmailAndPassword = jest.fn(() => Promise.resolve('result of signInWithEmailAndPassword'));
const signInWithRedirect = jest.fn(() => Promise.resolve('result of signInWithRedirect'));
const initializeApp = jest // eslint-disable-line no-unused-vars
.spyOn(firebase, 'initializeApp')
.mockImplementation(() => ({
auth: () => ({
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
currentUser: {
sendEmailVerification,
},
signInWithRedirect,
}),
}));
jest.spyOn(firebase, 'auth').mockImplementation(() => ({
onAuthStateChanged,
currentUser: {
displayName: 'testDisplayName',
email: 'test#test.com',
emailVerified: true,
},
getRedirectResult,
sendPasswordResetEmail,
}));
firebase.auth.FacebookAuthProvider = jest.fn(() => {});
firebase.auth.GoogleAuthProvider = jest.fn(() => {});
This file, I took from: https://github.com/mrbenhowl/mocking-firebase-initializeApp-and-firebase-auth-using-jest
The component I want to test is called EmailSignupLogin. In this particular case, I want to test the registerViaEmail-method:
methods: {
registerViaEmail() {
if (this.password.length > 0 && this.password === this.passwordReenter) {
firebase.auth().createUserWithEmailAndPassword(this.emailAdress, this.password).then((result) => {
const { user } = result;
console.log(result);
this.setUser(user);
this.$router.push('/stocks');
}).catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
this.error = errorMessage;
console.error(errorCode, errorMessage);
});
} else {
this.error = 'passwords not matching';
}
},
},
Now to my test file(email-signup-login.spec.js):
import { mount } from '#vue/test-utils';
import Vue from 'vue';
import EmailSignupLogin from '#/components/email-signup-login';
jest.mock('../../__mocks__/firebase');
describe('EmailSignupLogin', () => {
let wrapper;
const mockFunction = jest.fn();
beforeEach(() => {
wrapper = mount(EmailSignupLogin, {
data() {
return {
password: '123456',
passwordReenter: '123456',
emailAdress: 'test#test.com',
};
},
store: {
actions: {
setUser: mockFunction,
},
},
});
});
describe('methods', () => {
describe('#registerViaEmail', () => {
it('calls mockFunction', async () => {
await wrapper.vm.registerViaEmail();
expect(mockFunction).toHaveBeenCalled();
});
});
});
});
Inside the registerViaEmail-method I call the setUser-action, which is a vuex-action.
The problem is that it doesn't seem to call my mocked functions from __mocks__/firebase.js. Can somebody please tell me why?
Several issues turned up in your code:
registerViaEmail() is not async (not returning a Promise), so the await call returns prematurely, at which point your test tries to assert something that hasn't occurred yet. To resolve this, just wrap the function body with a Promise:
registerViaEmail() {
return new Promise((resolve, reject) => {
if (this.password.length > 0 && this.password === this.passwordReenter) {
firebase.auth().createUserWithEmailAndPassword(this.emailAdress, this.password).then((result) => {
//...
resolve()
}).catch((error) => {
//...
reject()
});
} else {
//...
reject()
}
})
},
The script you referred to is not intended to be used with Jest __mocks__. The script itself directly modifies the firebase object, replacing its methods/properties with mocks. To use the script, you just need to import it before importing the test module that uses firebase:
import './firebase-mock' // <-- order important
import EmailSignupLogin from '#/components/EmailSignupLogin'
createUserWithEmailAndPassword does not return anything. It looks like it originally returned the Promise, but you modified it with a console.log, and forgot to continue returning the Promise, which prevented this method from being awaited (same issue as #1). The solution there is to return the Promise:
const createUserWithEmailAndPassword = jest.fn(() => {
console.log('heeeeelllo')
return /*π*/ Promise.resolve(/*...*/)
})
createUserWithEmailAndPassword is the method to be tested in EmailSignupLogin, but it's currently not mocked in your auth mock object. It's only mocked in the return of initializeApp.auth, but that's not what it being used in EmailSignupLogin. To resolve the issue, copy createUserWithEmailAndPassword to your auth mock object:
jest.spyOn(firebase, 'auth').mockImplementation(() => ({
onAuthStateChanged,
currentUser: {
displayName: 'testDisplayName',
email: 'test#test.com',
emailVerified: true,
},
getRedirectResult,
sendPasswordResetEmail,
createUserWithEmailAndPassword, //π
}));
In your test setup, you mocked the store with a plain object, but it actually needs to be an instance of Vuex.Store:
mount({
//store: { /*...*/ }, //βDON'T DO THIS
store: new Vuex.Store({ /*...*/ }) //β
})
Github demo
Related
This is my helper function in src/utils/calls/aws.js
export const loginCognito = (cognitoUser, authenticationDetails) => {
return new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: resolve,
onFailure: reject,
newPasswordRequired: resolve,
});
});
};
This is my test file:
import { fireEvent, screen, waitFor } from "#testing-library/react";
import { LoginPage } from "../../../views/LoginPage/LoginPage";
import { renderWithProviders } from "../../__test-utils__/test-utils";
// eslint-disable-next-line jest/no-mocks-import
import { LOCALSTORAGE_ACCESS_TOKEN } from "../../../utils/constants/constant";
const accessToken = "an.access.token";
const promisedResult = {
"idToken": {
"jwtToken": "an.access.token",
"payload": {
"sub": "354548-5454-c59637523bfd",
"email_verified": true,
"iss": "https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_tomahwaf",
"cognito:username": "john-doe",
"preferred_username": "john-doe",
"aud": "client.id.aws",
"custom:dataInizioContratto": "01/01/2020",
"event_id": "ee5626d5-ffc2-4ecf-89df-2e1a83c0c174",
"token_use": "id",
"auth_time": 1670256322,
"custom:azienda": "acme",
"exp": 1670259922,
"iat": 1670256322,
"email": "info#example.com"
}
},
"refreshToken": {
"token": "a.refresh.token"
},
"accessToken": {
"jwtToken": "an.access.token",
"payload": {
"sub": "354548-5454-c59637523bfd",
"event_id": "ee5626d5-ffc2-4ecf-89df-2e1a83c0c174",
"token_use": "access",
"scope": "aws.cognito.signin.user.admin",
"auth_time": 1670256322,
"iss": "https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_tomahwaf",
"exp": 1670259922,
"iat": 1670256322,
"jti": "083d4b9a-5042-485b-bb65-c66e8ae5b921",
"client_id": "client.id.aws",
"username": "john-doe"
}
},
"clockDrift": 0
};
jest.mock("../../../utils/calls/aws.js", () => ({
//loginCognito: jest.fn().mockReturnValue(Promise.resolve({accessToken: "an.access.token"}))
//loginCognito: jest.fn(() => Promise.resolve(promisedResult))
//loginCognito: jest.fn(() => promisedResult)
loginCognito: jest.fn().mockResolvedValue(promisedResult)
}));
describe("Test LoginPage", () => {
it("Can press button without userEvent", async () => {
renderWithProviders(<LoginPage />);
const inputUsername = screen.getByLabelText(/username/i);
fireEvent.change(inputUsername, {
target: {
value: "info#example.com",
},
});
const inputPassword = screen.getByLabelText(/password/i);
fireEvent.change(inputPassword, {
target: {
value: "thisIsASecretPassword",
},
});
const loginButton = screen.getByRole("button", { name: /login/i });
fireEvent.click(loginButton);
await waitFor(() => {
expect(localStorage.getItem(LOCALSTORAGE_ACCESS_TOKEN)).toEqual(
accessToken
);
});
});
});
In the real file (LoginPage) I have
const handleClick = async () => {
// Fetch the data from cognito
const {cognitoUser, authenticationDetails} = loginCognitoUser(currentItem);
// Call AWS
try {
const isLogged = await loginCognito(cognitoUser, authenticationDetails);
console.log(">>>>>>>>>>>>>>>>>>>", JSON.stringify(isLogged))
} catch (e) {
console.log(e)
}
};
For real implementation it works, but when I call from the test I get
console.log
>>>>>>>>>>>>>>>>>>> undefined
at handleClick (src/views/LoginPage/LoginPage.js:52:15)
Doesn't mock of a function "bypasses" it returning what I want?
I have made 4 tests, I don't know anymore what to do:
//loginCognito: jest.fn().mockReturnValue(Promise.resolve({accessToken: "an.access.token"}))
//loginCognito: jest.fn(() => Promise.resolve(promisedResult))
//loginCognito: jest.fn(() => promisedResult)
loginCognito: jest.fn().mockResolvedValue(promisedResult)
I tried add a console.log inside my aws function and... If I remove the mocked I have the console.log, so I think that function is correctly mocked but cannot have the return.
Edit nr. 2
I tried another thing:
export const loginCognito = () => {
return "ciao"
}
And in test:
jest.mock("../../../utils/calls/aws.js", () => ({
loginCognito: jest.fn().mockReturnValue("addio")
}));
The page under test has console.log undefined!
try {
//const isLogged = await loginCognito(cognitoUser, authenticationDetails);
const isLogged = loginCognito();
console.log("==========", isLogged)
} catch (e) {
console.log(e)
}
Am I that I don't know how mock it works?
Console
console.log
========== undefined
at handleClick (src/views/LoginPage/LoginPage.js:55:15)
I need to bypass totally the AWS funciton...
I can't really speak for why your code is not working, but this is how I would go about writing your test:
// import the function you want to mock
import { loginCognito } from "../../../utils/calls/aws.js";
// tell jest to mock your module
jest.mock("../../../utils/calls/aws.js");
// before each test, mock the implementation
beforeEach(() => {
loginCognito.mockResolvedValue(...);
});
// cleanup after each test
afterEach(() => {
loginCognito.mockClear(...);
});
If you're using typescript, you will need to make typescript aware that the method is mocked. This is done by casting the method, which is mostly a tedium required for typescript. As a matter of convention, I usually put the work "mock" in front of the name:
const mockLoginCongnito = loginCognito as jest.MockedFunction<typeof loginCognito>;
Friends, I have a problem!
I'm testing my API with NestJS.
I'm using Jest for the test. Unfortunately, I am encountering the following error:
TypeError: Cannot read properties of undefined (reading 'close')
This error is very explicit but I don't see where it could come from.
Would you have an idea?
My current code :
import * as pactum from 'pactum';
import { Test } from '#nestjs/testing';
import { AppModule } from '../src/app.module';
import { INestApplication, ValidationPipe } from '#nestjs/common';
import { PrismaService } from '../src/prisma/prisma.service';
import { AuthDto } from '../src/auth/dto';
describe('App e2e', () => {
let app: INestApplication;
let prisma: PrismaService;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
const app = moduleRef.createNestApplication();
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
await app.init();
await app.listen(3334);
prisma = app.get(PrismaService);
await prisma.cleanDatabase();
});
afterAll(async () => {
console.log('Closing server');
await app.close(); // <------------- THE PROBLEM ARISES HERE.
});
describe('Auth', () => {
describe('Signup', () => {
it('should signup a user', () => {
const dto: AuthDto = {
email: 'darrel.doe#mail.com',
password: '1234',
};
return pactum
.spec()
.post('http://localhost:3333/auth/signup')
.withBody(dto)
.expectStatus(201);
});
});
describe('Signin', () => {
it.todo('should signin a user');
});
});
});
You are mixing two variables in different scopes.
let app: INestApplication; is upper scope one which you are actually using, since it does not have any value assigned it is undefined. The inner one is different because you are defining inside another scope.
A solution is very simple, just remove const from const app = moduleRef.createNestApplication();
I have a simple class-based component that I'm trying to convert to a function-based component, but am running into all kinds of dead ends.
My component is a straightforward adaptation of the boilerplate gifted-chat package, and uses Watson Assistant as a backend to provide responses. There's nothing complex about the backend part, these are just thin wrappers on Watson Assistants's API:
getSessionID = async (): Promise<string>
gets a session ID for use in communicating with the backend, and
sendReply = async (reply: string, sessionID: string): Promise<string>
returns Assistant's response to the string provided as a reply. These are not the source of the trouble I'm having (the bodies of both could be replaced with return await "some string" and I'd have the same issues): the class-based version (below) works perfectly.
But I'm at a loss to figure out how to convert this to a functional form, in particular:
I'm struggling to find a suitable replacement for componentWillMount. Using useEffect with sessionID as state results in errors: getMessage gets called (even if I await) before the required sessionID is set.
I can avoid this by not making sessionID state (which it arguably shouldn't be) and just making it a global (as in the functional attempt below). But even if I do this:
After each user reply, and receipt of a response, the user reply is removed from the conversation, so that the entire conversation just consists of generated replies.
Both of these problems are, I think, linked to the lack of callbacks in the hook-based state setting idiom, but the issue could also lie elsewhere. In any case, I'm at a loss to know what to do.
Chatter.tsx (working class based version)
import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
class Chatter extends React.Component {
state = {
messages: [],
sessionID: null,
}
componentWillMount() {
WatsonAssistant.getSessionID()
.then((sID) => {
this.setState( {
sessionID: sID,
} )
} )
.then(() => this.getMessage(''))
.catch((error) => {
console.error(error)
} )
}
onSend = (message = []): void => {
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ), () => {
this.getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
} )
}
getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, this.state.sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ))
}
render() {
return (
<GiftedChat
messages={ this.state.messages }
onSend={ messages => this.onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
}
export default Chatter
Chatter.tsx (failed function based attempt)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage(''))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages)
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
}
const getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(messages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Chatter.tsx (working function based version)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage('', []))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages) // Apparently, no waiting goes on here
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '), newMessages)
}
const getMessage = async (text: string, currentMessages): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(currentMessages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Ok, since I don't have your full code I'm not sure this will just work as-is (in particular without the types from your dependencies I'm not sure if/how much the compiler will complain), but should give you something you can adapt easily enough.
const reducer = ({ messages }, action) => {
switch (action.type) {
case 'add message':
return {
messages: GiftedChat.append(messages, action.message),
};
case 'add sent message':
return {
// Not sure if .append is variadic, may need to adapt
messages: GiftedChat.append(messages, action.message, action.message[0].text.replace(/[\n\r]+/g, ' ')),
}
}
};
const Chatter = () => {
const [sessionID, setSessionID] = useState(null);
const [messages, dispatch] = useReducer(reducer, []);
const getMessage = async (text: string, sessionID: number, type: string = 'add message'): Promise<void> => {
const response = await WatsonAssistant.sendReply(text, sessionID);
const message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
};
dispatch({
type,
message,
});
};
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => (setSessionID(sID), sID))
.then(sID => getMessage('', sID))
.catch((error) => {
console.error(error)
});
}
fetchData();
}, []);
return (
<GiftedChat
messages={messages}
onSend={messages => getMessage(messages, sessionID, 'add sent message')}
user={{
_id: 1,
}}
/>
);
};
Main difference is useReducer. As far as I can tell in the original code you had two actions: append this message or append this message and then a copy of it with the text regex replaced. I've used different dispatches to the reducer to handle the cases rather than the callback to setState. I've modified your attempt at useEffect, here I'm (ab)using the comma operator to return the ID returned from the service so that it can be fed directly to getMessage as a parameter rather than relying on state that hasn't been updated yet.
I'm still kinda skeptical in general about the hooks API, but assuming this works I actually think it simplifies the code here.
I'm trying to create a test that will see if signIn has been called, then proceed to the success and error function testing.
I'm using the firebase-mock package here:
https://github.com/soumak77/firebase-mock/blob/master/tutorials/auth/authentication.md
Below is my Login action
// Sign in action
export const signIn = (email, password, redirectUrl = ROUTEPATH_DEFAULT_PAGE) => (dispatch) => {
dispatch({ type: USER_LOGIN_PENDING });
firebase
.then(auth => auth.signInWithEmailAndPassword(email, password))
.catch((e) => {
console.error('actions/Login/signIn', e);
// Register a new user
if (e.code === LOGIN_USER_NOT_FOUND) {
dispatch(push(ROUTEPATH_FORBIDDEN));
dispatch(toggleNotification(true, e.message, 'error'));
} else {
dispatch(displayError(true, e.message));
setTimeout(() => {
dispatch(displayError(false, ''));
}, 5000);
throw e;
}
})
.then(res => res.getIdToken())
.then((idToken) => {
if (!idToken) {
dispatch(displayError(true, 'Sorry, there was an issue with getting your token.'));
}
dispatch(onCheckAuth(email));
dispatch(push(redirectUrl));
});
};
My test:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { MockFirebase } from 'firebase-mock';
// Login Actions
import { onCheckAuth, signIn } from 'actions';
// String Constants
import { LOGIN_USER_NOT_FOUND } from 'copy';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
let mockProps;
describe('login actions', () => {
// console.log('MockFirebase', MockFirebase);
// console.log('onCheckAuth', onCheckAuth);
let mockAuth;
beforeEach(() => {
mockAuth = new MockFirebase();
console.log('mockAuth: ==>', mockAuth);
mockProps = {
signIn: jest.fn(),
signOut: jest.fn(),
checkAuth: jest.fn(),
createUser: jest.fn(),
resetPassword: jest.fn(),
verifyEmail: jest.fn()
};
});
it('signIn should be called', () => {
const user = {
email: 'first.last#yum.com',
password: 'abd123'
};
signIn(user.email, user.password);
console.log('signIn', signIn);
expect(signIn).toHaveBeenCalled();
});
});
Error message
FAIL client/actions/Login/index.test.js
β login actions βΊ signIn should be called
expect(jest.fn())[.not].toHaveBeenCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function signIn]
at Object.<anonymous> (client/actions/Login/index.test.js:71:29)
I was incorrectly mocking the firebase services function, below is code I got working, however running into a new issue posted here: How to test is code inside of thenable in jest test is getting called?
The following test passes, however not sure that the code inside of the store.dispatch is thenable...
// Mock all the exports in the module.
function mockFirebaseService() {
return new Promise(resolve => resolve(true));
}
// Since "services/firebase" is a dependency on this file that we are testing,
// we need to mock the child dependency.
jest.mock('services/firebase', () => new Promise(resolve => resolve(true)));
describe('login actions', () => {
let store;
beforeEach(() => {
store = mockStore({});
});
it('signIn should call firebase', () => {
const user = {
email: 'first.last#yum.com',
password: 'abd123'
};
store.dispatch(signIn(user.email, user.password)).then(() => {
expect(mockFirebaseService).toHaveBeenCalled();
});
});
});
I have been following these testing guidelines to test my vuex store.
But when I touched upon the actions part, I felt there is a lot going on that I couldn't understand.
The first part goes like:
// actions.js
import shop from '../api/shop'
export const getAllProducts = ({ commit }) => {
commit('REQUEST_PRODUCTS')
shop.getProducts(products => {
commit('RECEIVE_PRODUCTS', products)
})
}
// actions.spec.js
// use require syntax for inline loaders.
// with inject-loader, this returns a module factory
// that allows us to inject mocked dependencies.
import { expect } from 'chai'
const actionsInjector = require('inject!./actions')
// create the module with our mocks
const actions = actionsInjector({
'../api/shop': {
getProducts (cb) {
setTimeout(() => {
cb([ /* mocked response */ ])
}, 100)
}
}
})
I infer that this is to mock the service inside the action.
The part which follows is:
// helper for testing action with expected mutations
const testAction = (action, payload, state, expectedMutations, done) => {
let count = 0
// mock commit
const commit = (type, payload) => {
const mutation = expectedMutations[count]
expect(mutation.type).to.equal(type)
if (payload) {
expect(mutation.payload).to.deep.equal(payload)
}
count++
if (count >= expectedMutations.length) {
done()
}
}
// call the action with mocked store and arguments
action({ commit, state }, payload)
// check if no mutations should have been dispatched
if (expectedMutations.length === 0) {
expect(count).to.equal(0)
done()
}
}
describe('actions', () => {
it('getAllProducts', done => {
testAction(actions.getAllProducts, null, {}, [
{ type: 'REQUEST_PRODUCTS' },
{ type: 'RECEIVE_PRODUCTS', payload: { /* mocked response */ } }
], done)
})
})
This is where it I find it difficult to follow.
My store looks like:
import * as NameSpace from '../NameSpace'
import { ParseService } from '../../Services/parse'
const state = {
[NameSpace.AUTH_STATE]: {
auth: {},
error: null
}
}
const getters = {
[NameSpace.AUTH_GETTER]: state => {
return state[NameSpace.AUTH_STATE]
}
}
const mutations = {
[NameSpace.AUTH_MUTATION]: (state, payload) => {
state[NameSpace.AUTH_STATE] = payload
}
}
const actions = {
[NameSpace.ASYNC_AUTH_ACTION]: ({ commit }, payload) => {
ParseService.login(payload.username, payload.password)
.then((user) => {
commit(NameSpace.AUTH_MUTATION, {auth: user, error: null})
})
.catch((error) => {
commit(NameSpace.AUTH_MUTATION, {auth: [], error: error})
})
}
}
export default {
state,
getters,
mutations,
actions
}
And This is how I am trying to test:
import * as NameSpace from 'src/store/NameSpace'
import AuthStore from 'src/store/modules/authorization'
const actionsInjector = require('inject!../../../../../src/store/modules/authorization')
// This file is present at: test/unit/specs/store/modules/authorization.spec.js
// src and test are siblings
describe('AuthStore Actions', () => {
const injectedAction = actionsInjector({
'../../Services/parse': {
login (username, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve({})
} else {
reject({})
}
}, 300)
})
}
}
})
it('Gets the user profile if the username and password matches', () => {
const testAction = (action, payload, state, mutations, done) => {
const commit = (payload) => {
if (payload) {
expect(mutations.payload).to.deep.equal(payload)
}
}
action({ commit, state }, payload)
.then(result => {
expect(state).to.deep.equal({auth: result, error: null})
})
.catch(error => {
expect(state).to.deep.equal({auth: [], error: error})
})
}
testAction(injectedAction.login, null, {}, [])
})
})
If I try to do this, I get:
"Gets the user profile if the username and password matches"
undefined is not a constructor (evaluating 'action({ commit: commit, state: state }, payload)')
"testAction#webpack:///test/unit/specs/store/modules/authorization.spec.js:96:13 <- index.js:26198:14
webpack:///test/unit/specs/store/modules/authorization.spec.js:104:15 <- index.js:26204:16"
I need help understanding what am I supposed to do to test such actions.
I know it's been awhile but I came across this question because I was having a similar problem. If you were to console.log injectedActions right before you make the testAction call you'd see that the injectedAction object actually looks like:
Object{default: Object{FUNC_NAME: function FUNC_NAME(_ref) { ... }}}
So the main solution here would be changing the testAction call to:
testAction(injectedAction.default.login, null, {}, [], done)
because you are exporting your action as defaults in your store.
A few other issues that are unrelated to your particular error... You do not need to manipulate the testAction boilerplate code. It will work as expected so long as you pass in the proper parameters. Also, be sure to pass done to testAction or your test will timeout. Hope this helps somebody else who comes across this!