Jest with moxios keeps timing out when I use custom axios instance - unit-testing

I have a service that uses a custom axios instance that I am trying to test but I keep getting an error.
Here is the error:
: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Here is the test:
import moxios from 'moxios';
import NotificationService, { instance } from '../NotificationService';
beforeEach(() => {
moxios.install(instance);
});
afterEach(() => {
moxios.uninstall(instance);
});
const fetchNotifData = {
data: {
bell: false,
rollups: []
}
};
describe('NotificationService.js', () => {
it('returns the bell property', async done => {
const isResolved = true;
const data = await NotificationService.fetchNotifications(isResolved);
moxios.wait(() => {
let request = moxios.requests.mostRecent();
console.log(request);
request
.respondWith({
status: 200,
response: fetchNotifData
})
.then(() => {
console.log(data);
expect(data).toHaveProperty('data.bell');
done();
});
});
});
});
And here is the code that I'm trying to test:
import axios from 'axios';
// hardcoded user guid
const userId = '8c4';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
console.log('NotificationService.fetchNotifications()', res);
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried shortening the jest timeout and that did not work. I think it is moxios not installing the axios instance properly, but I can't find any reason why it wouldn't.
Any help is appreciated, thanks in advance.

have you tried changing the Jest environment settings by adding this to your test file?
/**
* #jest-environment node
*/
import moxios from 'moxios';
...
Jest tends to prevent the requests from going out unless you add that. Either way, I use nock instead of moxios and I recommend it.

Related

Vue Jest - Create Mock Api server

I want to create a Mock API Server for my Jest tests so that I can define all my backend endpoints and create responses and authentication checks.
I have managed to set up the server and routes by following some of the source code from Chris Fritz "Vue-Enterprice-boilerplate":
https://github.com/chrisvfritz/vue-enterprise-boilerplate/tree/master/tests/unit
// jest.config.js
const _ = require("lodash");
process.env.MOCK_API_PORT = process.env.MOCK_API_PORT || _.random(9000, 9999);
module.exports = {
preset: "#vue/cli-plugin-unit-jest",
setupFiles: ["./tests/unit/setup"],
globalSetup: "<rootDir>/tests/unit/global-setup",
globalTeardown: "<rootDir>/tests/unit/global-teardown",
testMatch: ["**/(*.)spec.js"],
moduleFileExtensions: ["js", "jsx", "json", "vue"],
transform: {
"^.+\\.vue$": "vue-jest",
"^.+\\.js$": "babel-jest",
".+\\.(css|scss|jpe?g|png|gif|webp|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)$":
"jest-transform-stub"
},
transformIgnorePatterns: ["/node_modules/(?!vue-spinner)"],
testURL: process.env.API_BASE_URL || `http://localhost:${process.env.MOCK_API_PORT}`
};
The server runs when the tests starts and I can console log the route files.
I just don't know how the axios call from my Vuex would go with the mock API instead of the real one.
Might need to import axios somewhere in the test to prevent the development URL to be used?
/tests/mock-api/routes/auth.js
const Users = require("../resources/users");
module.exports = app => {
console.log('I can see this during tests!');
app.post("/api/v1/login", async (req, res) => {
console.log("I don't see this..");
await Users.authenticate(req.body)
.then(user => {
res.json(user);
})
.catch(error => {
res.status(401).json({ message: error.message });
});
});
});
// /views/Login.spec.js
import Vue from "vue";
import Vuelidate from "vuelidate";
import Login from "#/views/Login";
import BaseButton from "#/components/Globals/_base-button.vue";
import BaseInput from "#/components/Globals/_base-input.vue";
import BaseLabel from "#/components/Globals/_base-label.vue";
import flushPromises from "flush-promises";
import store from "#/store";
import { shallowMount } from "#vue/test-utils";
Vue.use(Vuelidate);
describe("#/views/Login", () => {
// other tests..
it("redirects to posts on successful login", async () => {
const wrapper = shallowMount(Login, { store, stubs: { BaseInput, BaseButton, BaseLabel } });
wrapper.vm.$v.$touch();
const spyDispatch = jest.spyOn(wrapper.vm.$store, "dispatch");
const username = wrapper.find("#username");
const password = wrapper.find("#password");
username.element.value = "johndoe#email.com";
password.element.value = "passwordz";
username.trigger("input");
password.trigger("input");
await wrapper.find("#submitBtn").trigger("click.prevent");
await wrapper.vm.$nextTick();
await flushPromises();
await expect(spyDispatch).toHaveBeenCalledWith("auth/login", {
username: username.element.value,
password: password.element.value
});
// #TODO add expect for redirect as well
});
// /store/auth.js (vuex)
export const actions = {
async login({ commit }, { username, password }) {
console.log("I see this");
const response = await axios.post("/login",
{ username, password }, { withCredentials: true });
console.log("I don't see this");
// #TODO error handling
if (!response) return;
commit("setUser", { ...response.data.user });
router.push({ name: "Posts" });
},
The login action gets called but I don't get passed the axios.post.
Do I need to import axios somewhere to make sure I get a fresh instance? (Vuex uses one I set the baseURL and headers)
All the other tests and logic works except this.

Jest mock twilio - how to?

I have been using Jest to do my unit tests with node.
I am used to mocking the first level of the modules/functions, but on the challenge to mock Twilio, I am not having so much luck.
I am using the twilio method: client.messages.create, so here I have the twilio client from the constructor require('twilio')(account sid, token), and the first layer is from the object/method(?) messages, and last the third level create, and it's this last guy that I am trying to mock.
I was trying something like this:
jest.mock('twilio', () => {
const mKnex = {
messages: jest.fn(),
};
return jest.fn(mKnex);
});
However, I am not able to mock the client resolved value, where I get client.message.create is not a function.
If I try the above mock plus this client.messages.create.mockReturnValueOnce({sid: "FOO", status: "foo"); I get that cannot read the property create from undefined(messages).
Any tip, post, docs that could give me some luck on this?
Thanks
The solution for this is:
Create a file for Twilio client:
// sms.client.ts
import { Twilio } from 'twilio';
const smsClient = new Twilio(
'TWILIO-ACCOUNT-SID',
'TWILIO-TOKEN'
);
export { smsClient };
Then, your service file should look like this:
// sms.service.ts
import { smsClient } from './sms.client';
class SMSService {
async sendMessage(phoneNumber: string, message: string): Promise<string> {
const result = await smsClient.messages.create({
from: '(555) 555-5555',
to: phoneNumber,
body: message,
});
if (result.status === 'failed') {
throw new Error(`Failed to send sms message. Error Code: ${result.errorCode} / Error Message: ${result.errorMessage}`);
}
return result.sid;
}
}
export const smsService = new SMSService();
Last but not least, your spec/test file needs to mock the client file. E.g.
// sms.service.spec.ts
import { MessageInstance, MessageListInstance } from 'twilio/lib/rest/api/v2010/account/message';
import { smsClient } from './sms.client';
import { smsService } from './sms.service';
// mock the client file
jest.mock('./sms.client');
// fixture
const smsMessageResultMock: Partial<MessageInstance> = {
status: 'sent',
sid: 'AC-lorem-ipsum',
errorCode: undefined,
errorMessage: undefined,
};
describe('SMS Service', () => {
beforeEach(() => {
// stubs
const message: Partial<MessageListInstance> = {
create: jest.fn().mockResolvedValue({ ...smsMessageResultMock })
};
smsClient['messages'] = message as MessageListInstance;
});
it('Should throw error if response message fails', async () => {
// stubs
const smsMessageMock = {
...smsMessageResultMock,
status: 'failed',
errorCode: 123,
errorMessage: 'lorem-ipsum'
};
smsClient.messages.create = jest.fn().mockResolvedValue({ ...smsMessageMock });
await expect(
smsService.sendMessage('(555) 555-5555', 'lorem-ipsum')
).rejects.toThrowError(`Failed to send sms message. Error Code: ${smsMessageMock.errorCode} / Error Message: ${smsMessageMock.errorMessage}`);
});
describe('Send Message', () => {
it('Should succeed when posting the message', async () => {
const resultPromise = smsService.sendMessage('(555) 555-5555', 'lorem-ipsum');
await expect(resultPromise).resolves.not.toThrowError(Error);
expect(await resultPromise).toEqual(smsMessageResultMock.sid);
});
});
});
I've found a solution. It's still calling the endpoint, but for each twilio account, you get a test SID and Token, I used this one so it does not send a sms when testing with this:
if (process.env.NODE_ENV !== 'test') {
client = require('twilio')(accountSid, authToken)
listener = app.listen(3010, function(){
console.log('Ready on port %d', listener.address().port)
})
}else{
client = require('twilio')(testSid, testToken)
}

Cannot log after tests are done in jestjs

I have written test cases for signin API using jest. After completing all five test of a test suit jest give me following error in log.
Can any body tell Why it is So and how to fix it?
CODE:(signup.test.ts)
import request from 'supertest';
import { TYPES } from '../src/inversify.types'
import { Application } from '../src/app/Application'
import { container } from '../src/inversify.config'
import dotenv from 'dotenv'
import { RESPONSE_CODE } from '../src/utils/enums/ResponseCode'
import { RESPONSE_MESSAGES } from '../src/utils/enums/ResponseMessages'
import { UserSchema } from '../src/components/user/User';
// import jwt from 'jsonwebtoken';
var application: Application
describe("POST / - SIGNUP endpoint", () => {
// var testusers: any;
//This hook is executed before running all test cases, It will make application instance, make it to listen
// on it on port 3000 and add test document in DB
beforeAll(async () => {
// Make enviroment variables available throughout the application
dotenv.config();
// Getting application instance using iversify container
application = container.get<Application>(TYPES.Application);
// Initialize frontside of application
await application.bootstrap();
// Starting Application server on given port
await application.listen(3000);
});
afterAll(
//This hook is executed after running all test cases and delete test document in database
async () =>{
const res = await UserSchema.deleteMany({ Name: { $in: [ "Test User", "Test" ] } });
// `0` if no docs matched the filter, number of docs deleted otherwise
console.log('---------------------->>>>>>>>>>>>>>>>>>>', (res as any).deletedCount);
}
)
it("Signup for user that don\'t exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CREATED);
expect(JSON.parse(response.text)).toEqual(expect.objectContaining({
Message: RESPONSE_MESSAGES.ADDED_SUCESSFULLY,
Data: expect.objectContaining({
Name: 'Test User',
Country: '',
PhoneNumber: '',
// Password: '$2b$10$nIHLW/SA73XLHoIcND27iuODFAArOvpch6FL/eikKT78qbShAl6ry',
Dob: '',
Role: 'MEMBER',
IsEmailVerified: false,
IsBlocked: 'ACTIVE',
IsTokenSent: false,
twoFAStatus: false,
// _id: '5c812e2715e0711b98260fee',
Email: JSON.parse(process.env.TEST_USER).Email
})
})
);
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
it("Signup for user that exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CONFLICT);
expect(JSON.parse(response.text)).toEqual({
Message: RESPONSE_MESSAGES.ALREADY_EXISTS
})
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
});
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with
--detectOpenHandles to troubleshoot this issue.
Cannot log after tests are done. Did you forget to wait for something
async in your test?
Attempted to log "{ accepted: [ 'unverifiedtestuser#abc.com' ],
rejected: [],
envelopeTime: 621,
messageTime: 867,
messageSize: 906,
response: '250 2.0.0 OK 1551945300 f6sm5442066wrt.87 - gsmtp',
envelope:
{ from: 'abc#gmail.com',
to: [ 'unverifiedtestuser#abc.com' ] },
messageId: '<45468449-b5c8-0d86-9404-d55bb5f4g6a3#gmail.com>' }".
at CustomConsole.log (node_modules/jest-util/build/CustomConsole.js:156:10)
at src/email/MailHandler.ts:2599:17
at transporter.send.args (node_modules/nodemailer/lib/mailer/index.js:226:21)
at connection.send (node_modules/nodemailer/lib/smtp-transport/index.js:247:32)
at callback (node_modules/nodemailer/lib/smtp-connection/index.js:435:13)
at stream._createSendStream (node_modules/nodemailer/lib/smtp-connection/index.js:458:24)
at SMTPConnection._actionSMTPStream (node_modules/nodemailer/lib/smtp-connection/index.js:1481:20)
at SMTPConnection._responseActions.push.str (node_modules/nodemailer/lib/smtp-connection/index.js:968:22)
at SMTPConnection._processResponse (node_modules/nodemailer/lib/smtp-connection/index.js:764:20)
at SMTPConnection._onData (node_modules/nodemailer/lib/smtp-connection/index.js:570:14)
I was using the react-native default test case (see below) when Cannot log after tests are done happened.
it('renders correctly', () => {
renderer.create(<App />);
});
Apparently, the problem was that the test ended but logging was still needed. So I tried to make the callback in the test case async, hoping that the test won't terminate immediately:
it('renders correctly', async () => {
renderer.create(<App />);
});
And it worked. However, I have very little clue what the inner working is.
If you are using async/await type in your code, then this error can occur when you are calling async function without await keyword.
In my case, I have defined a function like this below,
async getStatistics(headers) {
....
....
return response;
}
But I have called this method like getStatistics(headers) instead of await getStatistics(headers).
When I included await, it worked fine and the issue resolved.
In my case while using nodejs + jest + supertest the problem was that when I import app from "./app" to my test file to do some stuff with supertest (request(app)), I actually import with app.listen() , because when I'm exporting app, export takes in account app.listen() too, but we don't need app.listen() in tests and it throws an error
"Cannot log after tests are done.Did you forget to wait for something async in your test?"
Here is all in one file(that's the problem!)
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});
export default app;
To solve this issue I created 2 separate folders:
// 1) app.ts
Where I put all stuff for my const app = express(), routes etc and export app
dotenv.config();
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
export default app;
// 2) index.ts
Where I put app.listen() and mongoose.connection() and import app
*import mongoose from "mongoose";
import app from "./app";
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});*
For me I needed to add an await before the expect() call also to stop this error (and an async before the test() callback function).
Also caused and fixed Jest not detecting coverage on the lines in the code throwing the error!
test("expect error to be thrown for incorrect request", async () => {
await expect(
// ^ added this
async () => await getData("i-made-this-up")
).rejects.toThrow(
"[API] Not recognised: i-made-this-up"
);
});
getData() returns an Axios call and in this case an error is caught by catch and re-thrown.
const getData = async (id) => {
return await axios
.get(`https://api.com/some/path?id=${id}`)
.then((response) => response.data)
.catch((error) => {
if (error?.response?.data?.message) {
console.error(error) // Triggered the error
throw new Error("[API] " + error.response.data.message);
}
throw error;
});
};
This happened to me because I had an infinite loop while (true). In my case, I was able to add a method for setting the value of the loop based on user input, rather than defaulting to true.
In my case, the error was caused by asynchronous Redis connection still online. Just added afterall method to quit Redis and could see the log again.
Working on Typescript 4.4.2:
test("My Test", done => {
let redisUtil: RedisUtil = new RedisUtil();
let redisClient: Redis = redisUtil.redis_client();
done();
});
afterAll(() => {
redisClient.quit();
});
I solved it with the env variables:
if (process.env.NODE_ENV !== 'test') {
db.init().then(() => {
app.listen(PORT, () => {
console.log('API lista por el puerto ', PORT)
})
}).catch((err) => {
console.error(err)
process.exit(1)
})
} else {
module.export = app
}
I faced same warnings. However the fix is bit weird:
The jest unit test script import a function (which is not export from src/). After I added the export to the function to be tested. The error disappears.
I had a similar issue:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ".
It was due to a missing static keyword. This code caused the issue:
class MyComponent extends React.Component<Props, State> {
propTypes = {
onDestroy: PropTypes.func,
}
}
It should have been:
class MyComponent extends React.Component<Props, State> {
static propTypes = {
onDestroy: PropTypes.func,
}
}

Im trying to mock a function from a service but Jest keeps calling the actual function instead of the mock function

I'm using Jest to test a function from a service that uses axios to make some api calls. The problem is that Jest keeps calling the actual services function instead of the mocked service function. Here is all of the code:
The tests:
// __tests__/NotificationService.spec.js
const mockService = require('../NotificationService').default;
beforeEach(() => {
jest.mock('../NotificationService');
});
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
expect.assertions(1);
const data = await mockService.fetchNotifications();
console.log(data);
expect(data).toHaveProperty('data.bell');
});
});
The mock:
// __mocks__/NotificationService.js
const notifData = {
bell: false,
rollups: [
{
id: 'hidden',
modifiedAt: 123,
read: true,
type: 'PLAYLIST_SUBSCRIBED',
visited: false,
muted: false,
count: 3,
user: {
id: 'hidden',
name: 'hidden'
},
reference: {
id: 'hidden',
title: 'hidden',
url: ''
}
}
],
system: [],
total: 1
};
export default function fetchNotifications(isResolved) {
return new Promise((resolve, reject) => {
process.nextTick(() =>
isResolved ? resolve(notifData) : reject({ error: 'It threw an error' })
);
});
}
The service:
import axios from 'axios';
// hardcoded user guid
export const userId = 'hidden';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
}
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried a couple of variations of using require instead of importing the NotificationService, but it gave some other cryptic errors...
I feel like I'm missing something simple.
Help me please :)
The problem is that Jest keeps calling the actual services function instead of the mocked service function.
babel-jest hoists jest.mock calls so that they run before everything else (even import calls), but the hoisting is local to the code block as described in issue 2582.
I feel like I'm missing something simple.
Move your jest.mock call outside the beforeEach and it will be hoisted to the top of your entire test so your mock is returned by require:
const mockService = require('../NotificationService').default; // mockService is your mock...
jest.mock('../NotificationService'); // ...because this runs first
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
...
});
});

Is it possible to use TypeScript with 'aws-sdk-mock'

I'm writing unit tests for a serverless application in TypeScript, and I'd like to mock the AWS SDK.
Unfortunately I have not found many existing type definitions for popular AWS mocking projects. In particular I'd like to use the aws-sdk-mock library, but without its type definitions I can't.
Theoretically I'd like to be able to do something like:
import 'jest';
import * as sinon from 'sinon';
import * as _ from 'lodash';
import { handler } from '../lib/lambda';
import AWSMock from 'aws-sdk-mock';
import { PutItemInput } from 'aws-sdk/clients/dynamodb';
const mockData: DataType = {
// ...some fields
};
describe('create data lambda tests', () => {
afterEach(() => {
sinon.restore();
AWSMock.restore();
});
it('returns a success response on creation', () => {
AWSMock.mock('DynamoDB.DocumentClient', 'put', (params: PutItemInput, callback: any) => {
return callback(null, 'Successful creation');
});
const mockGatewayEvent: any = {
headers: {
Authorization: // some JWT
},
body: _.clone(mockData)
};
handler(mockGatewayEvent).then((createdData: DataType) => {
expect(createdData.id).toBeDefined();
expect(createdData.id.length).toBeGreaterThan(0);
}, () => {
fail('The create request should not have failed');
});
});
});
Here's how we got it working with jest. This tests a lambda function that makes calls to Dynamo using the DynamoDB.DocumentClient.
The warnings about importing the aws-sdk-mock ts definitions go away for me if the file is called *.test.ts or *.spec.ts.
// stubbed.test.ts
// this line needs to come first due to my project's config
jest.mock("aws-sdk");
import * as AWS from "aws-sdk-mock";
import { handler } from "../index";
// these next two are just test data
import { mockDynamoData } from "../__data__/dynamo.data";
import { mockIndexData } from "../__data__/index.data";
describe("Stubbed tests", () => {
it("should return correct result when Dynamo returns one slice", async () => {
expect.assertions(2);
const mockQuery = jest.fn((params: any, cb: any) =>
cb(null, mockDynamoData.queryOneSlice)
);
AWS.mock("DynamoDB.DocumentClient", "query", mockQuery);
// now all calls to DynamoDB.DocumentClient.query() will return mockDynamoData.queryOneSlice
const response = await handler(mockIndexData.handlerEvent, null, null);
expect(mockQuery).toHaveBeenCalled();
expect(response).toEqual(mockIndexData.successResponseOneSlice);
AWS.restore("DynamoDB.DocumentClient");
});
});