Problem with testing async functions in Angular with Jasmine - javascript

I am unit testing my Ionic 4 app with Jasmine. At the moment I am getting errors when running almost all my tests because I am doing something wrong with the async/await functions. The error that I get is: "Error: Timeout - Async function did not complete within 5000ms." I have changed this timeout_interval to another larger number and I still get this error.
My code is:
beforeEach(async(() => {
const storage = new Storage({
// Define Storage
name: '__mydb',
driverOrder: ['indexeddb', 'sqlite', 'websql']
});
component = new HomepagePage(storage);
TestBed.configureTestingModule({
declarations: [ HomepagePage ],
imports: [
IonicModule.forRoot(),
RouterTestingModule,
IonicStorageModule.forRoot()
]
}).compileComponents();
fixture = TestBed.createComponent(HomepagePage);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', async () => {
let home = jasmine.createSpyObj('home_spy',['getprice'])
const result = await home.getprice
expect(component).toBeTruthy();
});
describe('before logged in', () => {
let home = jasmine.createSpyObj('home_spy',['getprice'])
it('shows the price', async () => {
const result = await home.getprice
expect(home.getprice.length).toEqual(2);
});
});
My app is working fine when I am using it. However, could it be that the error is in the code itself? An example of the getprice() function is:
async getprice() {
var price_eth :number
var price_btc :number
try {
var url_btc = "https://api.binance.com/api/v1/klines?symbol=BTCUSDT&interval=1m";
var url_eth = "https://api.binance.com/api/v1/klines?symbol=ETHUSDT&interval=1m"
const btc = await axios.get(url_btc)
const eth = await axios.get(url_eth)
if (btc.data.length != 0 && eth.data.length != 0) {
this.loaded = 'yes'
} else {
this.loaded='no'
}
this.time = new Date(btc.data[btc.data.length-1][0]).toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1");
price_btc = Math.floor(btc.data[btc.data.length-1][1])
price_eth = Math.floor(eth.data[eth.data.length-1][1])
//These global variables are declared so they display the price but they are not used for any other functions.
this.glob_price_btc = price_btc
this.glob_price_eth = price_eth
return {
'price_btc':price_btc,'price_eth':price_eth
}
} catch {
this.loaded = 'no';
return
}
}

I think you misunderstood the function beforeEach(). beforeEach() should be used to setup the test environment and will be called before an unittest will be exeuted.
Here is an example how it would be more correct and to setup the timeout duration.
describe("xyz test suite", () => {
//called before every unittest
beforeEach(function() {
//Do setup stuff
});
it('shows the price', async () => {
const result = await getSomething()
expect(result).toEqual(2);
});
}, 10000 <- Timeout in milliseconds)

Related

In Jest, how do I cause a function called within the function to return a specific value

This is the function I am testing (stripped down for simplicity's sake):
populate.js->
const { createSessionID } = require('./populate-template-utilities');
const createFile = async () => {
const responseHeader = {};
responseHeader.SessionID = createSessionID();
return responseHeader;
};
module.exports = {
createFile,
};
The function this function calls:
populate-template-utilities ->
const createSessionID = () => {
const digits = (Math.floor(Math.random() * 9000000000) + 1000000000).toString();
return `PAX${digits}`;
};
module.exports = {
createSessionID,
};
And my unit test (again stripped down):
const { createSessionID } = require('../app/lib/populate-template-utilities');
describe('create XML for output files', () => {
const mockID = jest
.spyOn(createSessionID)
.mockImplementation(() => 'PAX123456');
it('should create a PAX File', async () => {
const result = await createFile();
expect(result).toEqual(getFile);
});
});
I want createSessionID to return 'PAX123456' and think mockID should do it, but it's erroring with:
Cannot spy the undefined property because it is not a function; undefined given instead
The spyOn method needs at least two parameters: object and methodName.
Try sth like this:
import * as populateTemplateUtils from "../sessionStuff";
import { createFile } from "../createFile";
describe('create XML for output files', () => {
it('should create a PAX File', async () => {
jest
.spyOn(populateTemplateUtils, 'createSessionID')
.mockReturnValue('PAX123456');
const result = await createFile();
expect(result).toEqual({"SessionID": "PAX123456"});
});
});
It all started to work when I changed the:
module.exports = {
createSessionID,
};
to:
export const createSessionID = () => {

Mock call in Typescript in unit test using only Mocha

I have the following method:
import { ObjectDal } from "./ObjectDal";
export class ObjectBL {
async getObject(id) {
try {
let dal = new ObjectDal();
let result = await dal.get(id);
return result;
} catch (err) {
// log the error
}
}
where the ObjectDal class is:
export class ObjectDal {
async get(id) {
// open connection to db
// make a query based on id
// put the result in a `result` variable
return result;
}
}
I have to write an unit test for the getObject() method using only Mocha...
This is the begining of the UT:
const assert = require('assert');
const ObjectBL = require("../ObjectBL");
describe('Something', () => {
describe('...', () => {
it('getObject_GetsObjectUsingID_True', async () => {
// arange
let id = "123456789101";
let expected = {
"name": "ana",
"hasApples": true
};
let test = new ObjectBL.ObjectBL();
let result = await test.getObject(id);
assert.deepStrictEqual(result, expected);
});
});
});
But in this case I would have to call the method from the ObjectDal class...
How can I mock the call to the get() method using only Mocha?
I found answers with Sinon, or Mocha with Sinon and/or Chai... but nothing with only Mocha...
Proxies might be the way to go for you.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
You could mok methods by using a Proxy like so:
const assert = require('assert');
const ObjectBL = require("../ObjectBL");
describe('Something', () => {
describe('...', () => {
it('getObject_GetsObjectUsingID_True', async () => {
// arange
let id = "123456789101";
let expected = {
"name": "ana",
"hasApples": true
};
let test = new ObjectBL.ObjectBL();
const handler = {
get: function(obj, prop) {
// mok the getObject method
if(prop === 'getObject'){
return () => {
return Promise.resolve({
"name": "ana",
"hasApples": true
});
}
} else {
return obj[prop];
}
}
};
const p = new Proxy(test, handler);
let result = await p.getObject(id);
assert.deepStrictEqual(result, expected);
});
});
});
If you ONLY want to mok the ObjectDal.get method, you might want to override the prototype and recover it afterwards:
const assert = require('assert');
const ObjectBL = require("../ObjectBL");
const ObjectDal = require("../ObjectDal");
describe('Something', () => {
describe('...', () => {
it('getObject_GetsObjectUsingID_True', async () => {
// arange
let id = "123456789101";
let expected = {
"name": "ana",
"hasApples": true,
};
const proto = Object.getOwnPropertyDescriptor(ObjectDal, 'prototype').value;
const backup = proto.get;
proto.get = () => {
return Promise.resolve({
"name": "ana",
"hasApples": true,
});
}
let test = new ObjectBL.ObjectBL();
let result = await test.getObject(id);
ObjectDal.prototype.get = backup;
assert.deepStrictEqual(result, expected);
});
});
});
You could also override the ObjectDal with a Proxy and implement the construct handler to return a dummy ObjectDal, but this might be more tricky, since you are working with modules.
Testing is feedback, not just on whether or not your code works as advertised but even more crucially on the quality of your design.
The fact that you are having trouble writing the tests is your first sign you did something sub-optimal in the implementation. What you want is this:
export class ObjectBL {
constructor (dal) {
this.dal = dal;
}
async getObject(id) {
try {
let result = await this.dal.get(id);
return result;
} catch (err) {
// log the error
}
}
...and now the dependency is clear rather than implicit and will show up in editor tooltips, is more amenable to static analysis, etc. And it solves your problem: now you can mock it easily for testing, no further libraries needed.

How to mock request to service with database?

I'm new in Jest and don't understand how to mock request to service for unit test.
EmployeeController.js
const EmployeeService = require('../services/employeeService');
exports.getEmployeeById = (req, res) => {
EmployeeService.find(req.params.employeeId) // need to be mocked
.then((employee) => {
if (employee == 0 || '') {
return res.status(404).json({
success: false,
message: 'Employee not found!'
});
} else {
return res.status(200).json({
success: true,
employee: employee
});
}
}).catch(err => {
res.status(404).json({
success: false,
message: 'Employee not found!'
});
});
}
EmployeeService.find - returns to me from the database the employee object by the entered Id in url.
EmployeeService.js
const sql = require('../config/connection');
const Promise = require('bluebird');
const connection = require('../config/connection');
const Employee = require('../models/employee.model');
var queryAsync = Promise.promisify(connection.query.bind(connection));
Employee.find = async function (employeeId) {
var result = queryAsync(
"SELECT empID, empName, IF(empActive, 'Yes', 'No') empActive, dpName FROM Employee INNER JOIN Department ON empDepartment = dpID WHERE empID = ? ", employeeId);
return result;
}
employee.model.js - model of employee.
const Employee = function (emp) {
this.empName = emp.empName;
this.empActive = emp.empActive;
this.empDepartment = emp.empDepartment;
this.creator = emp.creator;
};
module.exports = Employee;
Jest has built in utilities for stubbing out dependencies.
const employeeService = require("../services/employeeService");
const employeeController = require("./employeeController");
describe("employeeController", () => {
beforeEach(() => {
// this mock can be overridden wherever necessary, eg by using
// employeeService.find.mockRejectedValue(new Error("oh no"));
// by default, resolve with something that meets the needs of your consuming
// code and fits the contract of the stubbed function
jest.spyOn(employeeService, "find").mockResolvedValue(someMockQueryResult);
});
afterEach(() => {
jest.restoreAllMocks();
});
// contrived test to show how jest stubs can be used
it("fetches the employee", async () => {
await employeeController.getEmployeeById(123);
expect(employeeService.find).toHaveBeenCalledWith(123);
});
});
There are also options for mocking out the entire module. Check out the jest docs:
https://jestjs.io/docs/en/mock-functions.html
https://jestjs.io/docs/en/manual-mocks.html

How to write Jasmine unit tests for this block of code

I need help understanding how to design test for this piece of code:
onSuccessHandler(response) {
if (response && this.cartModel.hasData(response)) {
const data = this.cartModel.parse(response);
this.cartModel.set(data);
cartChannel.request('render:cart');
}
}
I tried this, to see if request was called on cart channel, it seems to be passing, but code coverage still shows the block above is not covered :).
describe('Cart suite', () => {
let view;
const cartChannel = Radio.channel('cart');
beforeEach(() => {
view = new CartWidgetView({
model: new Model(fixturesObject),
cartModel,
});
view.render();
});
it('should request cart if response has data', () => {
//spyOn Channel in beforeEach method
view.cartModel = {
hasData: () => true,
parse: () => true,
set: (data) => {
view.data = data;
},
};
view.onButtonClick();
view.render();
const response = {};
cartChannel.trigger('render:cart', cartModel);
view.onSuccessHandler(response);
expect(cartChannel.request).toHaveBeenCalled();
expect(cartChannel.request).toHaveBeenCalledWith('get:model');
});
});
I'm new to TDD using Jasmine with MarionetteJS. The goal is to get it covered by test coverage.

Firebase function for dialogflow unittest not returning

I have the following firebase cloud function that does work as written.
'use strict';
const {
dialogflow,
Image,
} = require('actions-on-google')
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
const config = require('config');
// Initialize the Auth0 client
var AuthenticationClient = require('auth0').AuthenticationClient;
var auth0 = new AuthenticationClient({
domain: functions.config().familybank.auth0.domain,
clientID: functions.config().familybank.auth0.clientid
});
const app = dialogflow();
app.intent(config.get('dialogflow.intents.welcome_user'), async (conv) => {
const userInfo = await auth0.getProfile(conv.user.access.token)
.catch( function(err) {
console.error('Error getting userProfile from Auth0: ' + err);
conv.close("Something went wrong. Please try again in a few minutes. " + err)
});
// check for existing bank, if not present, create it
var bankRef = db.collection(config.get('firestore.bank_collection_key')).doc(userInfo.email);
const bankSnapshot = await bankRef.get()
if (bankSnapshot.exists) {
conv.ask("Welcome back to Family Bank. I just opened the bank I found for " + userInfo.name + ". How can I help you?");
} else {
// create new account
conv.ask("Welcome to Family Bank. I just created a new bank for " + userInfo.name + ". I can help you create and manage accounts. What would you like to do?");
}
})
exports.accessAccount = functions.https.onRequest(app);
I also wrote the following unittest that does successfully stub Auth0, Firestore, etc., but it fails to get a return value. Here's the test
const chai = require('chai');
const assert = chai.assert;
const sinon = require('sinon');
const admin = require('firebase-admin');
const test = require('firebase-functions-test')();
const config = require('config');
var UsersManager = require('auth0/src/auth/UsersManager');
describe('Cloud Functions', () => {
let myFunctions, adminInitStub, firestoreStub, collectionStub;
before(() => {
test.mockConfig({"familybank": {"auth0": {"domain": "mockdomain", "clientid": "mockid"}}});
adminInitStub = sinon.stub(admin, 'initializeApp');
oldFirestore = admin.firestore;
firestoreStub = sinon.stub();
Object.defineProperty(admin, 'firestore', { get: () => firestoreStub });
collectionStub = sinon.stub();
firestoreStub.returns({ collection: collectionStub });
sinon.stub(UsersManager.prototype, 'getInfo').callsFake( function fakeGetProfile() {
return Promise.resolve({"email": "someuser#domain.com", "name": "Joe Banks"});
});
myFunctions = require('../index');
});
after(() => {
adminInitStub.restore();
admin.firestore = oldFirestore;
test.cleanup();
});
describe('accessAccount', () => {
it('should return a 200', (done) => {
const bankOwnerParam = 'someuser#domain.com';
const bankStub = sinon.stub();
collectionStub.withArgs(config.get('firestore.bank_collection_key')).returns({ doc: bankStub });
bankStub.withArgs(bankOwnerParam).returns({ get: () => Promise.resolve({ mykey: 'mydata', exists: true })});
const req = {"responseId":"RESPONSEID","queryResult":{"queryText":"GOOGLE_ASSISTANT_WELCOME","action":"input.welcome","parameters":{},"allRequiredParamsPresent":true,"fulfillmentMessages":[{"text":{"text":[""]}}],"outputContexts":[{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_welcome"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_audio_output"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_input_type_voice"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_media_response_audio"}],"intent":{"name":"projects/familybank/agent/intents/65b6c584-be5d-456b-ad77-341abdb4dcb4","displayName":"Default Welcome Intent"},"intentDetectionConfidence":1.0,"languageCode":"en-us"},"originalDetectIntentRequest":{"source":"google","version":"2","payload":{"isInSandbox":true,"surface":{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.MEDIA_RESPONSE_AUDIO"}]},"requestType":"SIMULATOR","inputs":[{"rawInputs":[{"query":"Talk to Family Bank","inputType":"VOICE"}],"intent":"actions.intent.MAIN"}],"user":{"lastSeen":"2018-11-15T14:41:36Z","accessToken":"TOKEN","locale":"en-US","userId":"USERID"},"conversation":{"conversationId":"SESSIONID","type":"NEW"},"availableSurfaces":[{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.SCREEN_OUTPUT"},{"name":"actions.capability.WEB_BROWSER"}]}]}},"session":"projects/familybank/agent/sessions/SESSIONID"};
const res = {
response: (code) => {
assert.equal(code, 200);
done();
}
};
myFunctions.accessAccount(req, res);
});
});
})
When I run the test, I get the following failure
root#fc19e6bca144:/appfiles/functions# npm test
> familybank#0.0.1 test /appfiles/functions
> mocha --reporter spec
Cloud Functions
accessAccount
1) should return a 200
0 passing (3s)
1 failing
1) Cloud Functions
accessAccount
should return a 200:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/appfiles/functions/test/index.test.js)
npm ERR! Test failed. See above for more details.
I can't figure out why this doesn't return a value. I followed https://firebase.google.com/docs/functions/unit-testing#testing_http_functions, and followed the links to some related files.
Thanks to the hint in the comment above, I was able to get a working solution with the following change
it('should return a 200', async () => {
const bankOwnerParam = 'someuser#domain.com';
const bankStub = sinon.stub();
collectionStub.withArgs(config.get('firestore.bank_collection_key')).returns({ doc: bankStub });
bankStub.withArgs(bankOwnerParam).returns({ get: () => Promise.resolve({ mykey: 'mydata', exists: true })});
const req = {"responseId":"RESPONSEID","queryResult":{"queryText":"GOOGLE_ASSISTANT_WELCOME","action":"input.welcome","parameters":{},"allRequiredParamsPresent":true,"fulfillmentMessages":[{"text":{"text":[""]}}],"outputContexts":[{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_welcome"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_audio_output"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_input_type_voice"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_media_response_audio"}],"intent":{"name":"projects/familybank/agent/intents/65b6c584-be5d-456b-ad77-341abdb4dcb4","displayName":"Default Welcome Intent"},"intentDetectionConfidence":1.0,"languageCode":"en-us"},"originalDetectIntentRequest":{"source":"google","version":"2","payload":{"isInSandbox":true,"surface":{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.MEDIA_RESPONSE_AUDIO"}]},"requestType":"SIMULATOR","inputs":[{"rawInputs":[{"query":"Talk to Family Bank","inputType":"VOICE"}],"intent":"actions.intent.MAIN"}],"user":{"lastSeen":"2018-11-15T14:41:36Z","accessToken":"TOKEN","locale":"en-US","userId":"USERID"},"conversation":{"conversationId":"SESSIONID","type":"NEW"},"availableSurfaces":[{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.SCREEN_OUTPUT"},{"name":"actions.capability.WEB_BROWSER"}]}]}},"session":"projects/familybank/agent/sessions/SESSIONID"};
const res = {
response: (code) => {
assert.equal(code, 200);
}
};
await myFunctions.accessAccount(req, res);
});

Categories

Resources