Mocking axios using interceptors (issue) - javascript

When running the following test to check if the login endpoint is being called with given arguments...
jest.mock("axios", () => {
return {
create: () => {
return {
interceptors: {
request: { eject: jest.fn(), use: jest.fn() },
response: { eject: jest.fn(), use: jest.fn() },
},
};
},
};
});
const mockedAxios = axios as jest.Mocked<typeof axios>;
it("should call login endpoint with given arguments", async () => {
render(<MainContextProvider />);
const mockedResponse = {
succeeded: true,
token: "eyJhbGc",
firstName: "Test",
lastName: "Test",
email: "test#test.io",
roles: ["Client"],
};
const { login } = authApi();
mockedAxios.post.mockResolvedValue(mockedResponse);
await login("test#test.io", "1234");
expect(axios.post).toHaveBeenCalledWith(
`${process.env.REACT_APP_URL_IDENTITY}/login`
);
expect(axios.post).toHaveBeenCalled();
});
I have the following error regarding mockResolvedValue property:
TypeError: Cannot read properties of undefined (reading
'mockResolvedValue')
231 | const { login } = authApi();
232 |
> 233 | mockedAxios.post.mockResolvedValue(mockedResponse);
| ^
234 | await login("test#test.io", "1234");
authApi.js
const authApi = function () {
const context = this;
const identityService = axios.create({
baseURL: process.env.REACT_APP_URL_IDENTITY,
});
identityService.interceptors.request.use(
(config) => {
config.headers.authorization = context.state.securityToken || null;
return config;
},
(error) => Promise.reject(error)
);
return {
login: async (email, pinCode) => {
const request = { username: email, password: pinCode };
const { data } = await identityService.post("/login", request);
return data;
},
};
};
export default authApi;
Any ideas on how to fix this issue and make the test pass?

Related

Cannot destructure property 'data' of '(intermediate value)' as it is undefined -problem in jest

I write test in Jest for my app trying to test if the data downloads correctly from the api. These are the data of the movies.
export const programsMock: FetchProgramsParameters = {
meta: {
status: 200,
pagination: {
page: 1,
perPage: 15,
hasNext: true,
},
},
data: [
{
id: "1",
title: "No Abras la Puerta",
genres: ["Drama", "Komedi", "Komedi", "Drama", "Romantik"],
imdb: {
rating: "7.1",
},
playProviders: [],
},
]}
jest.mock("./server/services/api");
render(<App />);
const fetchProgramsMocked = jest.mocked(fetchAllProgramsParameters);
describe("GET ", () => {
it("responds with 200", async () => {
fetchProgramsMocked.mockResolvedValueOnce(programsMock);
expect(fetchProgramsMocked).toHaveBeenCalledTimes(1);
expect(screen.getByText(/No Abras la Puerta/i)).toBeTruthy();
});
But I get error in getData: Cannot destructure property 'data' of '(intermediate value)' as it is undefined.
getData:
export const getAllPrograms = async (category: string) => {
const { data } = await fetchAllProgramsParameters(category);
const programs: TVProgram[] = data.map((program) => {
return {
id: program.id,
title: program.title,
imageLandscape: program.imageLandscape,
genres: program.genres,
playProviders: program.playProviders,
imdb: program.imdb,
};
});
return programs;
};
async function fetchApi<T>(pathname: string, filter?: string) {
const response = await fetch(`${pathname}${filter}`);
if (response.status === 404) {
throw new NotFoundError();
}
if (response.status >= 400) {
throw new HttpError("Bad response", response.status);
}
return response.json() as Promise<T>;
}
async function fetchAllProgramsParameters(category: string) {
return fetchApi<FetchProgramsParameters>(
API_URL,
["orderBy=views", "programType=" + category].join("&")
);
}
Please help how to solve it

How to send request to apollo graphql server while doing integration testing in jest?

This is my server file.
In context I am not getting the request while my test is getting pass while test the required scenario.
export async function buildTestServer({
user,
headers,
roles,
}: {
user?: User;
headers?: { [key: string]: string };
roles?: Role;
}) {
const schema = await tq.buildSchema({
authChecker: AuthChecker,
validate: false,
resolvers: allResolvers(),
scalarsMap: [{ type: GraphQLScalarType, scalar: DateTimeResolver }],
});
const server = new ApolloServer({
schema,
context: async ({ req }) => {
const authHeader = headers?.authorization;
if (authHeader) {
const token = extractTokenFromAuthenticationHeader(authHeader);
try {
const user = await new UserPermissionsService(token).call();
return { req, user };
} catch {
return { req };
}
} else {
if (user) {
let capabilities: any = [];
if (roles) {
capabilities = roles.capabilities;
}
return {
req,
user: {
id: user.id,
customerId: user.customerId,
capabilities,
},
};
} else {
return { req };
}
}
},
});
return server;
}
And this is my test file from where I am sending the request to the server.
My test is getting passed but I am not getting the request headers. I want to check the the request. Can anybody help me out ?
const GET_LIST = `
query GetList($listId: String!) {
GetList(listId: $listId) {
id
}
}
`;
test('Get Lists', async () => {
const customer = await CustomerFactory.create();
const user = await UserFactory.create({ customerId: customer.id });
const list = await ListFactory.create({
customerId: customer.id,
});
const server = await buildTestServer({ user });
const result = await server.executeOperation({
query: GET_LIST,
variables: {
listId: list.id
},
});
var length = Object.keys(result.data?.GetList).length;
expect(length).toBeGreaterThan(0);
});

How can I pull gmail data with Vue.js

I'm essentially trying to create an application for myself that I can manage my emails etc. with Vue.js
I've managed to get authorisation working using an npm package called vue-googleapis
However I am now trying to pull data from my (or the users) gmail account but running into some errors.
Here's my existing code in my component:
<div class="gmail-init">
<h1>Google APIs example - oAuth2 (vuex)</h1>
<p>isReady: {{ gauthReady }}</p>
<p>isSignedIn: {{ isSignedIn }}</p>
<p v-if="isSignedIn && user">{{ user.getBasicProfile().getName() }}</p>
<button :disabled="isSignedIn || !gauthReady" #click="signIn">Sign In</button>
<button :disabled="!isSignedIn || !gauthReady" #click="signOut">Sign Out</button>
<button :disabled="!isSignedIn || !gauthReady" #click="disconnect">Disconnect</button>
<button :disabled="!isSignedIn || !gauthReady" #click="labels">Labels</button>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'GmailInit',
computed: {
...mapGetters('gauth', {
gauthReady: 'isReady',
isSignedIn: 'isSignedIn',
user: 'getUser'
})
},
mounted () {
this.$store.dispatch('gauth/init')
},
methods: {
...mapActions('gauth', {
signIn: 'signIn',
signOut: 'signOut',
disconnect: 'disconnect'
}),
async labels () {
console.log('labels called')
const auth = this.isSignedIn
// const response = await this.$google.api.client.youtube.playlists.list({
const gmail = this.$google.gmail({ version: 'v1', auth })
const response = await gmail.users.labels.list({
// mine: true
userId: 'me'
// part: "snippet",
})
const labels = response.data.labels
if (labels.length) {
console.log(response.result.items)
labels.forEach((label) => {
console.log(`- ${label.name}`)
})
// this.playlistItems = response.result.items
} else {
console.log('error')
}
}
}
}
</script>
and here's my code for the import (vuex/store)
const STATUS_LOADING = 'loading'
const STATUS_READY = 'ready'
export default {
namespaced: true,
state: {
status: STATUS_LOADING,
signedId: null,
user: null,
error: null
},
mutations: {
setStatus (state, status) {
state.status = status
},
setSignedIn (state, signedId) {
state.signedId = signedId
},
setError (state, error) {
state.error = error
},
setUser (state, user) {
state.user = user
}
},
actions: {
init (context) {
const google = this._vm.$google
const load = setInterval(function () {
if (google.isInit) {
context.commit('setStatus', STATUS_READY)
context.commit(
'setSignedIn',
google.api.auth2.getAuthInstance().isSignedIn.get()
)
google.api.auth2.getAuthInstance().isSignedIn.listen(function (signedId) {
context.commit('setSignedIn', signedId)
})
google.api.auth2.getAuthInstance().currentUser.listen(function (user) {
context.commit('setUser', user)
})
clearInterval(load)
}
})
},
async signIn (context) {
try {
await this._vm.$google.api.auth2.getAuthInstance().signIn()
} catch (e) {
console.error(e)
context.commit('setError', e.error)
}
},
async signOut (context) {
try {
await this._vm.$google.api.auth2.getAuthInstance().signOut()
} catch (e) {
console.error(e)
context.commit('setError', e.error)
}
},
async disconnect (context) {
try {
await this._vm.$google.api.auth2.getAuthInstance().disconnect()
} catch (e) {
console.error(e)
context.commit('setError', e.error)
}
}
},
getters: {
isReady (state) {
return state.status === STATUS_READY
},
isSignedIn (state) {
return state.signedId === true
},
getUser (state) {
return state.user
}
}
}
the main function that isn't working:
async labels () {
console.log('labels called')
const auth = this.isSignedIn
// const response = await this.$google.api.client.youtube.playlists.list({
const gmail = this.$google.gmail({ version: 'v1', auth })
const response = await gmail.users.labels.list({
// mine: true
userId: 'me'
// part: "snippet",
})
const labels = response.data.labels
if (labels.length) {
console.log(response.result.items)
labels.forEach((label) => {
console.log(`- ${label.name}`)
})
// this.playlistItems = response.result.items
} else {
console.log('error')
}
}

jest testing nodejs controller

I have the following controller
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { UserModel, isPasswordAllowed } from '../../models/User';
const saltRounds = 10;
function userController() {
function add(req, res) {
try {
if (req.body.administrator) {
res.status(400).json({
error: {
message: 'Bad Request',
},
});
return;
}
if (!isPasswordAllowed(req.body.password)) {
res.status(400).json({
error: {
message: 'La contraseña no cumple con los requisitos minimos',
},
});
return;
}
bcrypt.hash(req.body.password, saltRounds, async (err, hash) => {
if (err) {
res.status(500).json({ error: { code: '500', message: err.errmsg } });
return;
}
const user = new UserModel();
user.email = req.body.email.toLowerCase();
user.password = hash;
await user
.save()
.then(() => {
const token = jwt.sign(
{
username: user.email,
userId: user.id,
},
process.env.JWT_KEY,
{
expiresIn: '7d',
},
);
res.status(200).json({
message: 'Usuario Creado',
token,
email: user.email,
});
})
.catch((error) => {
if (error.code === 11000) {
res.status(400).json({
error: { code: '500', message: 'El correo ya existe' },
});
} else {
console.log(error);
res.status(500).json({ error: { code: '500', message: error.message } });
}
});
});
} catch (error) {
res.status(503).json({ error });
}
}
return {
add,
};
}
export default userController();
As you can expect this controller works great, the user is created in the database, but I have the following test:
import UserController from './UserController';
import { connect, closeDatabase, clearDatabase } from '../../__test__/db-handler';
describe('test UserController', () => {
const res = {};
beforeEach(async () => {
await connect();
res.send = jest.fn().mockReturnValue(res);
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
});
afterEach(async () => {
await clearDatabase();
});
afterAll(async () => {
await closeDatabase();
});
test('should return the expect api method', () => {
const userControllerApi = {
add: expect.any(Function),
};
expect(UserController).toMatchObject(userControllerApi);
});
test('should return 400 error bad request is body contains administrator: true', async () => {
const req = {
body: {
administrator: true,
},
};
await UserController.add(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledTimes(1);
expect(res.json).toHaveBeenCalledWith({
error: {
message: 'Bad Request',
},
});
});
test('should return 400 error bad request is password is not allow', async () => {
const req = {
body: {
password: '123456',
},
};
await UserController.add(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledTimes(1);
expect(res.json).toHaveBeenCalledWith({
error: {
message: 'La contraseña no cumple con los requisitos minimos',
},
});
});
// this test is not passing
test('should create an user and return a token', async () => {
const req = {
body: {
email: 'test#test.com',
password: 'Abc123456',
},
};
const expectObject = {
message: 'Usuario Creado',
email: 'test#test.com',
};
await UserController.add(req, res);
jest.useFakeTimers();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledTimes(1);
expect(res.json).toMatchObject(expectObject);
});
});
but the last test 'should create an user and return a token' never pass and I get the following:
● test UserController › should create an user and return a token
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: 200
Number of calls: 0
78 | jest.useFakeTimers();
79 |
> 80 | expect(res.status).toHaveBeenCalledWith(200);
| ^
81 | expect(res.json).toHaveBeenCalledTimes(1);
82 | expect(res.json).toMatchObject(expectObject);
83 | });
I also debbug this code in testing mode and as you can see in the following image, the code is enter in the res.status(200).json({ .... }), so I don't understand what it is happening here.
The problem is that you're mixing callbacks with async/await, meaning that execution of add() will be finished before the callback of bcrypt.hash has been finished. This results in res.status not to have been called yet in your test.
You can fix this by awaiting the bcrypt.hash call (it supports returning a promise by default):
// await hashing function instead of using callback
const hash = await bcrypt.hash(req.body.password, saltRounds);
const user = new UserModel();
user.email = req.body.email.toLowerCase();
user.password = hash;
// rest of the code ...

Testing express middleware

I have following code to test:
const Status = require('http-status-codes');
const passport = require('passport');
const Users = require('../models/users.js');
const authentication = {
// Authenticate User Middleware
authenticateUser: function authenticateUser(req, res, next) {
return passport.authenticate('bearer', { session: false, failWithError: false },
(err, user, info) => {
if (err) {
return res.status(500).json({ message: err });
}
if (user) {
return Users.findOne({ auth_ref: user.auth_ref })
.populate('groups')
.exec((e, doc) => {
if (e) {
return res.status(500).json({ message: e });
}
req.authInfo = info;
req.user = doc;
return next(null, doc, info);
});
}
return res.status(Status.UNAUTHORIZED).json({ message: 'Access denied' });
}
)(req, res, next);
},
};
module.exports = authentication.authenticateUser;
My test file:
const test = require('ava');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const Util = require('../util');
Util.beforeEach(test, (t) => {
const authenticateStub = sinon.stub();
const passportStub = {
authenticate: authenticateStub,
};
const authenticationMocked = proxyquire('../../../middleware/authentication', { passport: passportStub });
t.context.authenticateStub = authenticateStub;
t.context.authenticationMocked = authenticationMocked;
});
Util.afterEach(test);
Util.after(test);
test('[middleware/authentication] authenticateUser function call succeed', sinon.test(async (t) => {
// given
const func = t.context.authenticationMocked;
t.context.authenticateStub.withArgs(sinon.match.any, sinon.match.any, sinon.match.any).yields('error', { statusCode: 500 }, 'sampleUser');
const nextSpy = sinon.spy();
const fakeReq = { user: { email: '' } };
const res = {
status: () => res,
json: () => res,
};
// when
func(fakeReq, res, nextSpy);
// then
})
My problem is that I somehow can't mock the res parameter in a way so that no error occurs.
This code produces the following error:
Rejected promise returned by test. Reason:
TypeError {
message: 'passport.authenticate(...) is not a function', }
If I remove the res object to {} the error is res.status is not a function
Am I doing something wrong with the initialization or is my res object wrong?
I now found the following solution:
// given
const func = t.context.authenticationMocked;
t.context.authenticateStub.withArgs(sinon.match.any, sinon.match.any, sinon.match.any).yields('error', { statusCode: 500 }, 'sampleUser').returns(() => {});
const nextSpy = sinon.spy();
const fakeReq = { user: { email: '' } };
const rootRouteStub = {
status: sinon.stub(),
json: sinon.spy(),
};
rootRouteStub.status.returns(rootRouteStub);
// when
func(fakeReq, rootRouteStub, nextSpy);

Categories