Why cannot read instance when unit testing? - javascript

*This app is for practice only.
I am trying to create multiple classes here that make up a chat room.
I create app instance as singleton instance.
App instance basically work as single module where it stores all users and rooms of the chat app.
This is what app instance looks like.
class App {
constructor() {
this.users = [];
this.rooms = [];
}
addUser(user) {
const duplicateUser = this.users.find(
(appUser) => appUser.userName === user.userName
);
if (duplicateUser) {
throw new Error("User already exists.");
}
this.users.push(user);
}
createUser(userName) {
const newUser = new User(userName);
this.addUser(newUser);
return newUser;
}
getUser(userName) {
return this.users.find((user) => user === userName);
}
....
}
const app = new App();
// Object.freeze(app); Commented out for testing purpose.
class User {
constructor(userName) {
this.userName = userName;
this.joinedRoomName = null;
}
createRoom(roomName) {
const duplicateRoom = app.getRoom(roomName);
const duplicateUser = app.getUser(this.userName);
if (duplicateRoom) {
throw new Error("The room already exists.");
}
if (duplicateUser) {
throw new Error("The user is already in another room");
}
const host = new Host(this.userName);
const room = new Room(roomName, host);
app.addRoom(room);
return room;
}
...other methods
I added a new user instance by using createUser above in the App.
When user creates an instance called room, as defined in user method,
it uses app's method(not static, but imported the instance) "getRoom and getUser".
However I get reference error like so:
TypeError: Cannot read property 'getRoom' of undefinedJest
Below is my jest code.
What is the issue here?
beforeEach(() => app.reset());
describe("클래스 인스턴스 생성 테스트", () => {
it("app 은 App 의 인스턴스여야 합니다.", () => {
expect(app).toBeInstanceOf(App);
});
it("App 에서 새로운 유저를 만들었을 때 앱에 해당 유저가 등록되어야 합니다.", () => {
const testUser = app.createUser("test");
expect(testUser.userName).toEqual(app.users[0].userName);
});
});
describe("방 개설과 방 참여 테스트", () => {
it("방을 개설했을 때 방은 App 에 추가되어야 합니다.", () => {
app.createUser("test");
const testUser = app.users[0];
const testRoom = testUser.createRoom("testRoom");
expect(testRoom.roomName).toEqual(app.rooms[0].roomName);
});
it("방 개설을 했을 때 그 방의 호스트는 개설한 유저 자신이어야 합니다.", () => {});
it("방 참여를 한다면 앱의 방에 해당 유저가 추가되어 있어야 합니다.", () => {});
it("다른 방에 속해있는 유저는 새로운 방을 개설할 수 없습니다.", () => {});
it("다른 방에 속해있는 유저는 새로운 방에 참여할 수 없습니다.", () => {});
it("사용자는 대화방을 나갈 수 있어야 합니다.", () => {});
});

Related

How do I make a constructor (module.exports) in JavaScript

I am working on making an npm module and it keeps responding with a "is not a constructor error". Here is my code:
const Discord = require('discord.js')
module.exports = function () {
this.Client = function () {
new Discord.Client()
}
this.Client.whenEvent = function (event, callback) {
const client = this.Client();
client.on(event, callback)
}
this.Client.login = function (token) {
const client = this.Client()
client.login(token)
}
}
and here is my testing code if you need it as well
const diss = require('./index.js')
const client = new diss.Client()
client.whenEvent('ready', () => {
console.log('IM READY!')
})
client.whenEvent('message', message => {
if (message.content == "test") {
message.channel.send('diss.js worked! NOW ADD STUFF!')
}
})
client.login(TOKEN)

Mocking Twilio Client from twilio-node package

I currently use The Twilio Node Helper Library to do various API calls whether it may be to create assistants/services, list them, remove them and various other things when it comes to uploading tasks, samples, fields to create a chatbot on Twilio Autopilot.
An example of one some of these functions include:
async function createAssistant(name, client){
var assistantUid = ""
await client.autopilot.assistants
.create({
friendlyName: name,
uniqueName: name
})
.then(assistant => assistantUid = assistant.sid);
return assistantUid
}
async function getAccount(client){
var valid = true
try {
await client.api.accounts.list()
} catch (e) {
valid = false
}
return valid
}
async function connectToTwilio(twilioAccountSid, twilioAuthToken) {
try{
var client = twilio(twilioAccountSid, twilioAuthToken);
} catch (e){
throw new TwilioRequestError(e)
}
var valid = await getAccount(client)
if(valid && client._httpClient.lastResponse.statusCode === 200){
return client
} else{
throw new Error("Invalid Twilio Credentials")
}
}
where client is the client object returned from require("twilio")(twilioAccountSid, twilioAuthToken).
I was wondering what would the best way of mocking this API to allow me to emulate creating assistants, returning their uniqueNames etc..
I was wondering that I may just define some class like
class TwilioTestClient{
constructor(sid, token){
this.sid = sid
this.token = token
this.assistants = TwilioAssistant()
this.services = TwilioServices()
}
}
Where TwilioAssitant and TwilioServices will be additional classes.
Any help would be greatly appreciated!
I struggled with mocking Twilio for a long time. In fact I previously architected my application such that I could mock a wrapper around the Twilio Node Helper just to avoid mocking the actual library. But recent changes to the architecture meant that was no longer an option. This morning I finally got a mock of the Twilio Node Helper Library working. I'm not familiar with the portions of the Twilio library you are using, but I'm hopeful the example here will help you.
We have a function to check if a phone number is mobile, call it isMobile.js.
const Twilio = require("twilio");
const isMobile = async (num) => {
const TwilioClient = new Twilio(process.env.TWILIO_SID, process.env.TWILIO_AUTH_TOKEN);
try {
const twilioResponse = await TwilioClient.lookups.v1
.phoneNumbers(num)
.fetch({ type: "carrier", mobile_country_code: "carrier" });
const { carrier: { type } = {} } = twilioResponse;
return type === "mobile";
} catch (e) {
return false;
}
};
module.exports = isMobile;
Then build a mock for Twilio in __mocks__/twilio.js
const mockLookupClient = {
v1: { phoneNumbers: () => ({ fetch: jest.fn(() => {}) }) }
};
module.exports = class Twilio {
constructor(sid, token) {
this.lookups = mockLookupClient;
}
};
In the test file isMobile.test.js
jest.mock("twilio");
const Twilio = require("twilio");
const isMobile = require("./isMobile");
const mockFetch = jest.fn();
const mockPhoneNumbers = jest.fn(() => ({
fetch: mockFetch
}));
describe("isMobile", () => {
const TwilioClient = new Twilio(process.env.TWILIO_SID, process.env.TWILIO_AUTH_TOKEN);
const lookupClient = TwilioClient.lookups.v1;
lookupClient.phoneNumbers = mockPhoneNumbers;
beforeEach(() => {
mockFetch.mockReset();
});
test("is a function", () => {
expect(typeof isMobile).toBe("function");
});
test("returns true for valid mobile number", async () => {
const validMobile = "+14037007492";
mockFetch.mockReturnValueOnce({
carrier: { type: "mobile", mobile_country_code: 302 }, // eslint-disable-line camelcase
phoneNumber: validMobile
});
expect(await isMobile(validMobile)).toBe(true);
});
test("returns false for non-mobile number", async () => {
const invalidMobile = "+14035470770";
mockFetch.mockReturnValueOnce({
carrier: { type: "not-mobile", mobile_country_code: null }, // eslint-disable-line camelcase
phoneNumber: invalidMobile
});
expect(await isMobile(invalidMobile)).toBe(false);
});
});

Firebase database ref on not updating at first

I'm building a chat app with react native and firebase.
When the user sign in, previous messages have to load at first launch but it provides empty arrays to promise at first then when I connect again, it shows up...
I'm using snack and I have to change code to see the chat histroy(In order to update the device).
How can I load at first launch?
edit:
firebase structure
userName:
-rooms:
-otherUser:
-messages
-name
Code:
firebase.database().ref(`userName/rooms`).on('child_added', snapshot => callback(parse(snapshot)));;
const parse = snapshot => {
//console.log(snapshot);
const { name } = snapshot.val();
const { key: _id } = snapshot;
const rooms = {
_id,
name
};
return rooms;
};
And this is happening in my useEffect:
const loadRequiredMaterials = async () => {
return new Promise(async (resolve, reject) => {
//var loadedRooms = [...rooms]
var loadedRooms = []
await on(oldRooms => {
loadedRooms = [oldRooms, ...loadedRooms]
});
await resolve([loadedRooms]);
})
}
if (firebase.auth().currentUser) {
console.log(firebase.auth().currentUser)
loadRequiredMaterials()
.then((result)=> {
console.log(result)
setRooms(result[0]);
})
}

Typescript Class Variable Not Updating / Retaining Value

I am trying to create a class that will fetch / cache users from my Firestore database. For some reason, I can't seem to save or expose the previous promise that was created. Here is my class:
export class UserCache {
private cacheTimeMilliseconds: number = 600000;
private userCache: any = {};
public getCacheUser(userid: string): Promise<User> {
return new Promise((resolve, reject) => {
let d = new Date();
d.setTime(d.getTime() - this.cacheTimeMilliseconds);
if (this.userCache[userid] && this.userCache[userid].complete && this.userCache[userid].lastAccess > d.getTime()) {
console.log("User cached");
resolve(this.userCache[userid].user);
}
console.log("Need to cache user");
this.userCache[userid] = {
complete: false
};
this.getSetUserFetchPromise(userid).then((data) => {
let user: User = <User>{ id: data.id, ...data.data() };
this.userCache[userid].user = user;
this.userCache[userid].complete = true;
this.userCache[userid].lastAccess = Date.now();
resolve(user);
});
});
}
private getSetUserFetchPromise(userid: string): Promise<any> {
console.log(this.userCache[userid]);
if (this.userCache[userid] && this.userCache[userid].promise) {
return this.userCache[userid].promise;
} else {
console.log("Creating new user fetch request.");
this.userCache[userid].promise = firestore().collection('users').doc(userid).get();
console.log(this.userCache[userid]);
return this.userCache[userid].promise;
}
}
}
Logs: (there are only 2 unique users, so should only be creating 2 new requests)
In the logs I can see that the promise is getting set in getSetUserFetchPromise, but the next time the function is called, the property is no longer set. I suspect it is either a scope or concurrency issue, but I can't seem to get around it.
I am calling getCacheUser in a consuming class with let oCache = new UserCache() and oCache.getCacheUser('USERID')
Edit following Tuan's answer below
UserCacheProvider.ts
import firestore from '#react-native-firebase/firestore';
import { User } from '../static/models';
class UserCache {
private cacheTimeMilliseconds: number = 600000;
private userCache: any = {};
public getCacheUser(userid: string): Promise<User> {
return new Promise((resolve, reject) => {
let d = new Date();
d.setTime(d.getTime() - this.cacheTimeMilliseconds);
if (this.userCache[userid] && this.userCache[userid].complete && this.userCache[userid].lastAccess > d.getTime()) {
console.log("User cached");
resolve(this.userCache[userid].user);
}
console.log("Need to cache user");
this.userCache[userid] = {
complete: false
};
this.getSetUserFetchPromise(userid).then((data) => {
let user: User = <User>{ id: data.id, ...data.data() };
this.userCache[userid].user = user;
this.userCache[userid].complete = true;
this.userCache[userid].lastAccess = Date.now();
resolve(user);
});
});
}
private getSetUserFetchPromise(userid: string): Promise<any> {
console.log(this.userCache[userid]);
if (this.userCache[userid] && this.userCache[userid].promise) {
return this.userCache[userid].promise;
} else {
console.log("Creating new user fetch request.");
this.userCache[userid].promise = firestore().collection('users').doc(userid).get();
console.log(this.userCache[userid]);
return this.userCache[userid].promise;
}
}
}
const userCache = new UserCache();
export default userCache;
ChatProvider.ts (usage)
let promises = [];
docs.forEach(doc => {
let message: Message = <Message>{ id: doc.id, ...doc.data() };
promises.push(UserCacheProvider.getCacheUser(message.senderid).then((oUser) => {
let conv: GCMessage = {
_id: message.id,
text: message.messagecontent,
createdAt: new Date(message.messagedate),
user: <GCUser>{ _id: oUser.id, avatar: oUser.thumbnail, name: oUser.displayname }
}
if (message.type && message.type == 'info') {
conv.system = true;
}
if (message.messageattachment && message.messageattachment != '') {
conv.image = message.messageattachment;
}
return conv;
}));
});
Promise.all(promises).then((values) => {
resolve(values);
});
Without seeing the calling code, it could be that getCacheUser is called twice before firestore resolves.
As an aside, I think refactoring the class may make debugging easier. I wonder why it caches the user, promise completion status, and the promise itself. Why not just cache the promise, something like:
interface UserCacheRecord {
promise: Promise<User>
lastAccess: number
}
export class UserCache {
private cacheTimeMilliseconds: number = 600000;
private userCache: { [userid: string]: UserCacheRecord } = {};
public async getCacheUser(userid: string): Promise<User> {
let d = new Date();
const cacheExpireTime = d.getTime() - this.cacheTimeMilliseconds
if (this.userCache[userid] && this.userCache[userid].lastAccess > cacheExpireTime) {
console.log("User cached");
return this.userCache[userid].promise
}
console.log("Need to cache user");
this.userCache[userid] = {
promise: this.getUser(userid),
lastAccess: Date.now()
}
return this.userCache[userid].promise
}
private async getUser(userid: string): Promise<User> {
const data = firestore().collection('users').doc(userid).get();
return <User>{ id: data.id, ...data.data() };
}
}
Currently, you create new UserCache everytime you access cache users. You have to export the instance of UserCache class, so just single instance is used for your app.
UserCache.ts
class UserCache {
}
const userCache = new UserCache();
export default userCache;
SomeFile.ts
import UserCache from './UserCache';
UserCache.getCacheUser('USERID')
Update
Added some tests
class UserCache {
userCache = {};
getUser(id) {
return new Promise((resolve, reject) => {
if (this.userCache[id]) {
resolve({
...this.userCache[id],
isCache: true,
});
}
this.requestUser(id).then(data => {
resolve(data);
this.userCache[id] = data;
});
});
}
requestUser(id) {
return Promise.resolve({
id,
});
}
}
const userCache = new UserCache();
export default userCache;
userCache.test.ts
import UserCache from '../test';
describe('Test user cache', () => {
test('User cached successfully', async () => {
const user1: any = await UserCache.getUser('test1');
expect(user1.isCache).toBeUndefined();
const user2: any = await UserCache.getUser('test1');
expect(user2.isCache).toBe(true);
});
});

Jest - stub function within function

I'm writing unit-tests, where I need to set a mock response for a function within a function.
This is the function I want to mock:
cassandraDriver.js
module.exports = ({
cassandra_user,
cassandra_password,
cassandra_address
}) => {
if (!cassandra_address.length) throw Error('Cassandra address is not valid')
return new Promise((resolve, reject) => {
try {
const client = new driver.Client({
contactPoints: cassandra_address.split(','),
authProvider: authProvider(cassandra_user, cassandra_password),
queryconfig: {
consistency: driver.types.consistencies.quorum
}
})
return resolve(client)
} catch (e) {
reject(e)
}
})
}
This is the file that uses it:
const {
cassandraDriver
} = require('./lib')
module.exports = async ({
username = 'cassandra', //default values
password = 'cassandra', //default values
address,
keyspace,
replication_factor = 1,
migration_script_path,
logger = require('bunyan').createLogger({name: 'BuildCassandra'})
} = {}) => {
try {
const client = await cassandraDriver(username, password, address)
}).catch(err => {
throw Error(err)
})
} catch (e) {
logger.error(e)
throw e
}
}
How can I mock the call to 'cassandraDriver' in unit-tests? I tried using rewire, but the method is not exposed as it normally would be.
Thanks in advance.
let's modify your function so that it can accept a mock driver instead of cassandraDriver
const {
cassandraDriver
} = require('./lib')
module.exports = async ({
username = 'cassandra',
password = 'cassandra',
address,
keyspace,
replication_factor = 1,
migration_script_path,
logger = require('bunyan').createLogger({
name: 'BuildCassandra'
}),
driver = cassandraDriver
} = {}) => {
try {
const client = await driver(
username,
password,
address
})
} catch (e) {
logger.error(e)
throw e
}
}
(i also removed a superfluous .catch block)
next, you should create a "cassandra-driver-mock.js" which emulates the behaviour of the cassandra driver for your unit tests
the unit tests, of course, would pass the mock instead of the real driver as an option parameter
You can stub the module which exports cassandraDriver in your test file:
import cassandraDriver from "<path-to-cassandraDriver.js>";
jest.mock("<path-to-cassandraDriver.js>", () => jest.mock());
cassandraDriver.mockImplementation(() => {
// Stub implementation and return value
});
See Manual Mocks for more information.

Categories

Resources