jest.mock('../redux/storeConfigure', () => () => ({
getState: () => ({ auth: { token: 'TEST_TOKEN' } })
}))
import { prepareRequestHeaders } from './fetch'
describe('prepareRequestHeaders', () => {
it('returns header with Authorization if token is set', () => {
expect(prepareRequestHeaders()['Authorization']).toBe('Bearer TEST_TOKEN')
})
it('returns header without Authorization if token is not set', () => {
?????
})
})
In prepareRequestHeaders I import ../redux/storeConfigure
How to remock ../redux/storeConfigure with other implementation?
EDIT:
/* fetch.js */
import { store } from '../app'
export const prepareRequestHeaders = () => {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
}
const { token } = store.getState().auth
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
return headers
}
/* app.js */
import storeConfigure from './redux/storeConfigure'
export const store = storeConfigure()
/* Directories strucutre */
- redux
- storeConfigure.js
- api
- fetch.js
- api.test.js
app.js
Something like this should work out:
let mockGetState;
jest.mock('../redux/storeConfigure', () => () => {
mockGetState = jest.fn().mockReturnValue({ auth: { token: 'TEST_TOKEN' } })
return { getState: mockGetState }
})
import { prepareRequestHeaders } from './fetch'
describe('prepareRequestHeaders', () => {
it('returns header with Authorization if token is set', () => {
expect(prepareRequestHeaders()['Authorization']).toBe('Bearer TEST_TOKEN')
})
it('returns header without Authorization if token is not set', () => {
mockGetState.mockReturnValueOnce({auth: {token: 'SOMETHING_ELSE'}})
// make your assertion
})
})
When you assign to a Jest mock (jest.fn()) you can change its return value whenever you want. The name of it has to start with mock because of mock hoisting
Related
I'm using Observable for updating token when its expiered.the process works properly and when token has been expiered it'll send a request and gets new token and then retries the last request .the request gets data correctly and I can see it in the network but when I'm trying to use the data in the component I get undefined with this error :
index.js:1 Missing field 'query name' while writing result {}
here is my config for apollo :
import {
ApolloClient,
createHttpLink,
InMemoryCache,
from,
gql,
Observable,
} from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
import { onError } from "#apollo/client/link/error";
import store from "../store";
import { set_user_token } from "../store/actions/login_actions";
const httpLink = createHttpLink({
uri: "http://myserver.com/graphql/",
});
const authLink = setContext((_, { headers }) => {
const token = JSON.parse(localStorage.getItem("carfillo"))?.Login?.token;
return {
headers: {
...headers,
"authorization-bearer": token || null,
},
};
});
const errorHandler = onError(({ graphQLErrors, operation, forward }) => {
if (graphQLErrors?.length) {
if (
graphQLErrors[0].extensions.exception.code === "ExpiredSignatureError"
) {
const refreshToken = JSON.parse(localStorage.getItem("carfillo"))?.Login
?.user?.refreshToken;
const getToken = gql`
mutation tokenRefresh($refreshToken: String) {
tokenRefresh(refreshToken: $refreshToken) {
token
}
}
`;
return new Observable((observer) => {
client
.mutate({
mutation: getToken,
variables: { refreshToken: refreshToken },
})
.then((res) => {
const token = res.data.tokenRefresh.token;
store.dispatch(set_user_token(token));
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
"authorization-bearer": token || null,
},
}));
})
.then(() => {
const subscriber = {
next: observer.next(() => observer),
error: observer.error(observer),
complete: observer.complete(observer),
};
return forward(operation).subscribe(subscriber);
});
});
}
}
});
export const client = new ApolloClient({
link: from([errorHandler, authLink, httpLink]),
cache: new InMemoryCache(),
});
I'm using vue3+typescript+pinia.
I am trying to follow the docs to crete tests but no success, got errors.
I want to test a store action which uses function that returns a promise.
EDITED:
The store pinia action
actions: {
async createContact(contact: Contact) {
console.log('this', this);
this.isLoading = true
ContactDataService.createContact(contact)
.then(response => {
this.sucess = true
console.log(response)
})
.catch(error => {
this.hasError = true
console.log(error);
})
this.isLoading = false
},
},
The exported class instance:
import Contact from "#/types/ContactType";
import http from "../http-commons";
class ContactDataService {
createContact(contact: Contact): Promise<any> {
const headers = {
"Content-Type": "application/json",
"accept": "*/*",
"Access-Control-Allow-Origin": "*"
}
return http.post("/contact", contact, { headers });
}
}
export default new ContactDataService();
The test:
import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach, vi } from "vitest";
import { useContactStore } from '#/stores/ContactStore'
import ContactDataService from "../../services/ContactDataService"
import Contact from '#/types/ContactType';
vi.mock('../../services/ContactDataService', () => {
const ContactDataService = vi.fn()
ContactDataService.prototype.createContact = vi.fn()
return { ContactDataService }
})
const contactExample: Contact = {
firstName: 'string',
lastName: 'string',
emailAddress: 'string',
}
describe('ContactStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('createContact', async () => {
const contactStore = useContactStore()
// expect(contactStore.sucess).toBeFalsy()
contactStore.createContact(contactExample)
// expect(contactStore.sucess).toBeTruthy()
})
})
When I run test I cant figure out how to mock the ContactDataService.createContact(contact) inside the action createContact.
Error: [vitest] No "default" export is defined on the "mock:/src/services/ContactDataService.ts"
I need to login without going through the login screen, that is, with mocked data, because when testing the login directly on the login screen, when authenticating I am redirected to the home screen, where in addition to not being the screen I want to test ends up breaking the test due to a problem with the iframe. Until then I already have the custom command to get the token and save it, I need to hit the url, and with the mocked data automatically log in, and with that redirect to the screen I want to test.
Here's what I have so far:
Commands.js
import 'cypress-iframe';
import 'cypress-dotenv';
import 'cypress-file-upload';
import 'cypress-localstorage-commands';
const defaultUsername = Cypress.env('user')
const defaultPassword = Cypress.env('password')
Cypress.Commands.add('getToken', (username = defaultUsername, password = defaultPassword) => {
cy.request({
method: 'POST',
url: 'https://testedt04.qa.directtalk.com.br/adminuiservices/api/Login',
headers: {
Authorization: `Basic ${window.btoa(`${username}:${password}`)}`,
},
}).then((res) => {
if (res.status === 200) {
cy.setLocalStorage('dt.admin.token', res.body.token)
cy.setLocalStorage('dt.admin.siteId', res.body.SiteId)
cy.setLocalStorage('dt.admin.agentId', res.body.AgentId)
cy.setLocalStorage('dt.admin.tenantId', res.body.tenantId)
cy.setLocalStorage('dt.admin.departments', JSON.stringify(res.body.departments))
cy.setLocalStorage('dt.admin.mnemonic', res.body.mnemonic)
cy.setLocalStorage('dt.admin.megaMenu', res.body.MegaMenu)
cy.setLocalStorage('dt.admin.siteName', res.body.siteName)
cy.setLocalStorage('dt.admin.agentUserName', res.body.agentUserName)
cy.setLocalStorage('dt.admin.agentName', res.body.agentName)
}
})
}
Login.spec.js
/// <reference types="cypress"/>
import signin from '../pages/SigninPage'
describe("Get Token And Save", () => {
before(() => {
cy.getToken();
cy.saveLocalStorage()
})
beforeEach(() => {
cy.restoreLocalStorage()
})
it("Should Exist Token in localStorage", () => {
cy.getLocalStorage("dt.admin.token").should("exist")
cy.getLocalStorage("dt.admin.token").then(token => {
cy.log('Token generated: ' + token)
})
})
})
describe('When I login in supervisor', () => {
before(() => {
signin.go()
cy.getToken()
signin.noException()
cy.saveLocalStorage()
})
beforeEach(() => {
cy.reload()
cy.restoreLocalStorage()
})
it("Sould Still Exist Token in localStorage", () => {
cy.getLocalStorage("dt.admin.token").should("exist")
cy.getLocalStorage("dt.admin.token").then(token => {
cy.log('Token generated: ' + token)
})
})
it('With CORRECTLY credentials', () => {
signin.login(Cypress.env('user'),Cypress.env('password'))
signin.verifyLogin(Cypress.env('site'),Cypress.env('agent'))
})
it('With WRONG credentials', () => {
signin.login('não existe','erro')
signin.verifyErrorLoginMessage(403, 'Credencias informadas invalidas')
})
it('With NOT FOUND response request', () => {
signin.interceptLogin(404)
signin.login('SEM','erro')
signin.verifyErrorLoginMessage(404, 'Operação não encontrada')
})
it('With SERVER ERROR response request', () => {
signin.interceptLogin(500)
signin.login('SEM','erro')
signin.verifyErrorLoginMessage(500, 'Erro interno no servidor')
})
})
SigninPage.js
class SigninPage {
go(){
cy.visit('/login.html')
}
login(user, pass){
this.go()
cy.get('#login').type(user)
cy.get('#password').type(pass)
cy.get('[ng-show="SHOW_INPUTS"] > .form-wrapper > .form-horizontal > .buttons-wrapper > #loginButton').click()
}
verifyLogin(site, agent){
cy.get('.site-name').should('have.text', site)
cy.get('.agent-name').should('have.text', agent)
}
interceptLogin(code){
cy.intercept('POST', '/adminuiservices/api/Login', {
statusCode: code,
body: {},
},)
}
verifyErrorLoginMessage(code, message){
cy.get(`[ng-show="hasError(${code})"]`)
.should('have.text', message)
}
noException(){
Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
}
}
export default new SigninPage;
I am not very familier yet on how to write unit tests via moxios and your help would be very much appreciated.
My request is the following:
export const walletRequest = () => {
return AWAxiosInstance()
.get(`${AW_BASE_URL}/account/wallet`)
.then(response => {
if (response) {
return formatAccountDetails(response.data);
}
})
.catch(error => {
return Promise.reject('Error requesting data from Account & Wallet API', error)
})
}
So basically here in the above function I'm trying to retrieve some data via an axios instance.
My understanding is that moxios is being used to mock the axios instance, but I am not very sure how to write the unit test for the walletRequest() function.
What I've tried:
import moxios from 'moxios'
import { walletRequest } from "../balance";
import AWAxiosInstance from '../../../../core/aw-axios-instance'
const responseMock = { balance: 100 };
describe("services/balance2", () => {
beforeEach(() => {
moxios.install(AWAxiosInstance)
})
afterEach(() => {
moxios.uninstall(AWAxiosInstance)
})
it("should call the walletRequest and retrieve data", () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response: {
responseMock
}
})
})
const response = walletRequest().response;
expect(response).toEqual(responseMock);
});
});
This doesn't work at this moment as the walletRequest() response is undefined.
What can I do?
Thank you in advance!
Solved this:
beforeEach(() => {
moxios.install(AWAxiosInstance)
formatAccountDetails.mockImplementation( () => responseMock)
})
afterEach(() => {
moxios.uninstall(AWAxiosInstance)
})
it("should return the data", async () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response: {
responseMock
}
})
})
const response = await walletRequest();
expect(response).toEqual(responseMock);
});
it('should not recieve response when request is rejected', () => {
const errorResp = {
status: 400,
response: { message: 'invalid data',
data: 'invalid data' }
};
const response = walletRequest();
moxios.wait(async() => {
let request = moxios.requests.mostRecent();
request.reject(errorResp);
response.then((err) => {
expect(err).toBe('invalid data');
});
});
});
I have a module called authProvider.js which I want to mock when I'm testing one of my functions in api.js.
I have set "automock": true in my jest config.
This is my structure
src
|-auth
| |-__mocks__
| | |-authProvider.js
| |-authProvider.js
|-utils
|-api.js
|-api.test.js
This is what I have tried so far but only having success with the first test case. I'm not sure how to set up the mocking in the second test case...
api.test.js
import { mockAuthProvider } from "../auth/__mocks__/authProvider";
import { getDefaultHeaders, getValueIndexByColumnName } from './api';
describe('API utils', () => {
describe('getDefaultHeaders', () => {
it('Not authenticated', async () => {
expect(await getDefaultHeaders()).toEqual({
'Content-Type': 'application/json'
});
});
it('Authenticated', async () => {
mockAuthProvider.getAccount.mockImplementationOnce(() =>
Promise.resolve({user: 'test'})
);
const headers = await getDefaultHeaders();
expect(mockAuthProvider.getAccount).toBeCalled();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
});
});
api.js
import { authProvider } from '../auth/authProvider';
import settings from '../settings';
export async function getDefaultHeaders() {
const account = await authProvider.getAccount();
const authenticationParameters = {
scopes: ['api://' + settings.AD_CLIENT_ID + '/login'],
redirectUri: window.location.origin + '/auth.html'
};
let token;
if (account) {
try {
token = await authProvider.acquireTokenSilent(authenticationParameters);
} catch (error) {
token = await authProvider.acquireTokenPopup(authenticationParameters);
}
}
if (token) {
return {
Authorization: `Bearer ${token.accessToken}`,
'Content-Type': 'application/json'
}
}
return {
'Content-Type': 'application/json'
}
}
__ mocks __/authProvider.js
const mockAuthProvider = {
getAccount: jest.fn(),
acquireTokenSilent: jest.fn(),
acquireTokenPopup: jest.fn()
};
module.exports = {
mockAuthProvider
};
Error message
Expected number of calls: >= 1
Received number of calls: 0
18 | const headers = await getDefaultHeaders();
19 |
> 20 | expect(mockAuthProvider.getAccount).toBeCalled();
| ^
UPDATE
I added a file to mock the whole module that exports the auth provider, but still not the best way to solve it I think. I'm having difficulties using it in multiple test cases since I need to specify the return values in a specific order.
Is there a better way to solve this issue?
__ mocks __/react-aad-msal.js
import React from 'react';
const errorObj = {
message: 'Some error'
};
export const mockGetAccount = jest.fn()
.mockReturnValueOnce(null) // Not authenticated
.mockReturnValueOnce({user: 'test'}) // Authenticated silent
.mockReturnValueOnce({user: 'test'}); // Authenticated popup
export const mockAcquireTokenSilent = jest.fn()
.mockReturnValueOnce({accessToken: 'abc123'}) // Authenticated silent
.mockRejectedValueOnce(errorObj); // Authenticated popup
export const mockAcquireTokenPopup = jest.fn()
.mockReturnValueOnce({accessToken: 'abc123'}); // Authenticated popup
export const MsalAuthProvider = jest.fn(() => ({
getAccount: mockGetAccount,
acquireTokenSilent: mockAcquireTokenSilent,
acquireTokenPopup: mockAcquireTokenPopup
}));
export const AuthenticationState = {
Authenticated: 'Authenticated',
Unauthenticated: 'Unauthenticated'
};
export const LoginType = {
Popup: 'popup'
};
export const AuthenticationActions = {
Initializing: 'Initializing',
Initialized: 'Initialized',
AcquiredIdTokenSuccess: 'AcquiredIdTokenSuccess',
AcquiredAccessTokenSuccess: 'AcquiredAccessTokenSuccess',
AcquiredAccessTokenError: 'AcquiredAccessTokenError',
LoginSuccess: 'LoginSuccess',
LoginError: 'LoginError',
AcquiredIdTokenError: 'AcquiredIdTokenError',
LogoutSucc: 'LogoutSucc',
AuthenticatedStateChanged: 'AuthenticatedStateChanged'
};
export const AzureAD = ({children}) => <div>{children}</div>;
The new api.test.js looks like this, note that the order of the tests now is important since the return values from the mock are in a fixed order.
import { getDefaultHeaders, axiosCreate, getValueIndexByColumnName } from './api';
describe('API utils', () => {
describe('getDefaultHeaders', () => {
it('Not authenticated', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
'Content-Type': 'application/json'
});
});
it('Authenticated silent', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
it('Authenticated popup', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
});
describe('axiosCreate', () => {
it('Create axios API base', () => {
expect(axiosCreate()).toBeTruthy();
});
});
describe('getValueIndexByColumnName', () => {
it('Invalid input data', () => {
expect(getValueIndexByColumnName([], null)).toEqual(null);
expect(getValueIndexByColumnName(['column1'], null)).toEqual(-1);
});
it('Valid input data', () => {
expect(getValueIndexByColumnName(['column1'], 'column')).toEqual(-1);
expect(getValueIndexByColumnName(['column1'], 'column1')).toEqual(0);
expect(getValueIndexByColumnName(['column1', 'column2', 'column3'], 'column2')).toEqual(1);
});
});
});