I'm trying to develop a NodeJS app connecting to Firebase. I can connect successfully, but I'm unable to figure how to manage the scope in the then call.
I'm using NodeJS 6.9.2
My test implementation looks like this:
const EventEmitter = require('events');
const fb = require('firebase')
class FireGateway extends EventEmitter {
constructor() {
super();
if ( this.instance ) {
return this.instance;
}
// INIT
var fbConfig = {
apiKey: "xxxxx",
authDomain: "xxxxx.firebaseapp.com",
databaseURL: "https://xxxxx.firebaseio.com/"
};
fb.initializeApp(fbConfig)
this.instance = this;
this.testvar = "aaa";
}
login() {
fb.auth().signInWithEmailAndPassword ("email", "pwd")
.catch(function(error) {
// Handle Errors here.
}).then( function(onresolve, onreject) {
if (onresolve) {
console.log(this.testvar);
// "Cannot read property 'testvar' of undefined"
this.emit('loggedin');
// error as well
}
})
}
}
module.exports = FireGateway;
------
...
var FireGateway = require('./app/fireGateway');
this.fireGW = new FireGateway();
this.fireGW.login();
....
Any idea how can I manage it?
The callback passed to then is being called asynchronously from another context, so the this doesn't correspond to the instantiated object.
Using ES6 arrow functions you can keep your object context, since an arrow function does not create its own this context.
By the way, the syntax you are using in the then method is not correct, then accepts two callbacks with one argument each one. Check the syntax here.
The catch before the then is not necessary as well I think, it would make more sense to put it at the end.
It would be something like this:
login() {
fb.auth().signInWithEmailAndPassword("email", "pwd")
.then(
(onResolve) => {
console.log(this.testvar);
this.emit('loggedin');
},
(onReject) = > {
// error handling goes here
});
}
On the other hand, it seems login method is doing an asynchronous operation, so you might want to wait for it to finish in your code. I would make the login method return a Promise, so you can wait for it outside:
login() {
return fb.auth().signInWithEmailAndPassword("email", "pwd")
...
}
Related
my setup looks like this
const sandbox = sinon.createSandbox();
test.afterEach.always(() => {
sandbox.restore();
});
test.serial( 'some test', t => {
new awvr(context)
})
test.serial( 'other test', t=> {
new awvr(other_context)
}
export class awvr {
private static isStubConfigured: boolean = false;
private readonly serviceContext: ServiceCtx;
constructor(ctx: ServiceCtx) {
this.serviceContext = ctx;
awvr.setupMockedIntegrationRequestHandler();
}
private static setupMockedIntegrationRequestHandler() {
if (!awvr.isStubConfigured) {
const requestStub = { post: sandbox.stub() };
requestStub.post.resolves({ body: { output: 'test-response-field' } });
const integrationRequestStub = sandbox.stub(requestUtils, 'getIntegrationRequest');
integrationRequestStub.returns((requestStub as unknown));
const mockLock = createMockRedlock();
sandbox.stub(redis, 'lockNode').resolves(mockLock);
awvr.isIntegrationStubConfigured = true;
}
}
when i run these tests i get an error saying the below along with stack trace
attempted to wrap x which is already wrapped
checkWrappedMethod
wrapMethod
stub
Sandbox.stub
Function.setupMockedIntegrationRequestHandler
new awvr
processTicksAndRejections
test.ts:601:3
wrapMethod
stub
Sandbox.stub
Function.setupMockedIntegrationRequestHandler
new awvr
processTicksAndRejections
This seems to imply there's some parallelization going on -- although i tried creating two separate sandboxes for each test and passing them through to be used by awvr and that didn't work either. I have tried calling sandbox.restore() in the awvr call as well, but issue persists -- Is there something sinon does under the hood that doesn't allow for this setup?
I have a Typescript-backed Express.js project that uses a singleton Redis client.
The singleton includes wrapper functions to Redis commands needed for my application (e.g., SADD).
Singleton
Here is a snippet of my singleton Redis client service, which is relevant to my question:
/**
* Set up a singleton class instance to interface
* with the Redis database, along with helper async
* functions that provide functionality.
*/
var redis = require("redis");
// https://github.com/redis/node-redis/issues/1673
type RedisClientType = ReturnType<typeof redis.createClient>;
type RedisClientOptionsType = Parameters<typeof redis.createClient>[0];
export class Redis {
private static instance: Redis;
private static client: RedisClientType;
constructor() {
if (Redis.instance)
return Redis.instance;
Redis.instance = this;
Redis.client = null;
}
/* ... */
async initializeClient(options: RedisClientOptionsType) {
Redis.client = redis.createClient(options);
Redis.client.on('connect', function() {
Redis.instance.log('Client connected');
});
Redis.client.on('error', function(err: Error) {
Redis.instance.log(`Could not communicate with Redis client [${err}]`);
});
await Redis.client.connect();
}
async shutdownClient() {
await Redis.client.quit();
}
async multi() {
await Redis.client.multi();
}
async exec() {
await Redis.client.exec();
}
/* ... */
async sAdd(k: string, v: string) {
return await Redis.client.sAdd(k, v);
}
}
Individual calls to sAdd, sMembers, etc. work fine. So the client itself is initialized correctly, and it is able to process basic Redis calls.
What I would like to do is perform some chained transactions, e.g., from a rudimentary POST request using the singleton Redis client service, process some data from an uploaded file, and then add some key-value pairs (to start):
import { Redis } from '#/service/redis';
/* ... */
export const myPost = async (req: Request, res: Response) => {
const redis = new Redis(); // initialized client, as defined above
const k = 'my-key';
const v = 'my-value';
await redis
.multi()
.sAdd(k, v)
.exec();
}
Problem
The problem is that I get two errors with the chained await ... call.
First error
The first error is related to the await keyword, just before the multi/sAdd/exec call:
'await' expressions are only allowed within async functions and at the top levels of modules.ts(1308)
This await is within the async-ed post function. (I assume that I need to this handle the results of the underlying Promise chain.)
Second error
The second error is related to the sAdd wrapper:
Property 'sAdd' does not exist on type 'Promise<void>'.ts(2339)
I tried to add a return type to the call to multi:
async multi(): Promise<RedisClientType> {
await Redis.client.multi();
}
But this did not resolve the error with sAdd.
Question
What changes do I make to the service/singleton, which would allow calls to wrapper functions to be chained?
so I'm using RabbitMQ for some Projects and i noticed that i ll use some duplicate code all the Time that's why i decided to make a Wrapper Class or Interface that have some function to use RabbitMQ direct without repeating the code all the time. i began to do this yesterday and i already had some Problems since i wanted to use OOP and Javascript can be complicated when using OOP (at least i think so)
I began with creating a class IRAbbitMQ with function init to initialize a connection and create a channel, i knew that i cant use nested classes so instead i wanted to use Factory functions, i tried to make the connection and channel a part of the class IRabbitMQ properties but i dont know why that gave me undefined when i create an instance of it
class IRabbitMQ {
constructor() {
this.init(rabbitMQServer); // rabbitMQServer for example 'localhost//5672'
}
// establish a Connection to RAbbitMQ Server
async init(host) {
try {
let connection = await amqplib.connect(host);
let channel = await connection.createChannel();
channel.prefetch(1);
console.log(' [x] Awaiting RPC requests');
this.connection = connection;
this.channel = channel;
}
catch(err) {
console.error(err);
}
}
// Close the Connection with RabbitMQ
closeConnection() {
this.connection.close();
}
log() {
console.log(this.connection);
}
EventPublisher() {
function init(IRabbit, publisherName) {
if(!IRabbit.connection) {
throw new Error('Create an Instance of IRabbitMQ to establish a Connection');
}
let ch = IRabbit.channel;
console.log(ch);
}
return {
init : init
}
}
}
var r = new IRabbitMQ();
r.log();
when i run the code the output is undefined, i dont know why since i m initializing the connection and channel properties in the init function and then called that function in the constructor so that should be initialized when i create an object of the Wrapper class. i wanted also to take some advices from you wether it is good to use classes or is there any other better way to create a Wrapper class or Interface for RabbitMQ to make it easy to use it and not have to duplicate Code.
Not really an answer, but I was able to successfully log the connection with this example code. I trimmed out other code to just focus on the .log() part that was logging a undefined.
Code is far from perfect, but works at least
const amqplib = require('amqplib');
class IRabbitMQ {
constructor() { }
async init(host) {
try {
const connection = await amqplib.connect(host);
const channel = await connection.createChannel();
channel.prefetch(1);
console.log(' [x] Awaiting RPC requests');
this.connection = connection;
this.channel = channel;
}catch(err) {
console.error(err);
}
}
log() {
console.log(this.connection);
}
}
async function createInstance(){
const instance = new IRabbitMQ();
try {
await instance.init('amqp://localhost');
}catch (e) {
throw new Error('OOPS!');
}
return instance;
}
async function runLogic() {
const r = await createInstance();
r.log();
}
runLogic().catch(console.log);
Just comment if you'd want me to give additional advice/tips, but this seems to work for me.
My problem is the following: I want to test a method that uploads a buch of data into an AWS S3 bucket. The problem is: I don't want to really upload data every time I am testing and I don't want to care about credentials sitting in the env. So I want to setup Sinon's fake-server module to simulate the upload and return the same results then S3 would. Sadly, it seems to be difficult to find a working example with code using async/await.
My test looks like this:
import {skip, test, suite} from "mocha-typescript";
import Chai from "chai";
import {S3Uploader} from "./s3-uploader.class";
import Sinon from "sinon";
#suite
class S3UploaderTest {
public server : Sinon.SinonFakeServer | undefined;
before() {
this.server = Sinon.fakeServer.create();
}
after() {
if (this.server != null) this.server.restore();
}
#test
async "should upload a file to s3 correctly"(){
let spy = Sinon.spy();
const uploader : S3Uploader = new S3Uploader();
const upload = await uploader.send("HalloWelt").toBucket("onetimeupload.test").toFolder("test/hw.txt").upload();
Chai.expect(upload).to.be.a("object");
}
}
Inside of the uploader.upload() method, I resolved a promise out of a callback. So how can I simulate the uploading-process?
Edit: Here is the code of the s3-uploader:
import AWS from "aws-sdk";
export class S3Uploader {
private s3 = new AWS.S3({ accessKeyId : process.env.ACCESS_KEY_ID, secretAccessKey : process.env.SECRET_ACCESS_KEY });
private params = {
Body: null || Object,
Bucket: "",
Key: ""
};
public send(stream : any) {
this.params.Body = stream;
return this;
}
public toBucket(bucket : string) {
this.params.Bucket = bucket;
return this;
}
public toFolder(path : string) {
this.params.Key = path;
return this;
}
public upload() {
return new Promise((resolve, reject) => {
if (process.env.ACCESS_KEY_ID == null || process.env.SECRET_ACCESS_KEY == null) {
return reject("ERR_NO_AWS_CREDENTIALS");
}
this.s3.upload(this.params, (error : any, data : any) => {
return error ? reject(error) : resolve(data);
});
});
}
}
Sinon fake servers are something you might use to develop a client that itself makes requests, instead of a wrapper around an existing client like AWS.S3, like you're doing. In this case, you're better off just stubbing the behavior of AWS.S3 instead of testing the actual requests it makes. That way you can avoid testing the implementation details of AWS.S3.
Since you're using TypeScript and you've made your s3 client private, you're going to need to make some changes to expose it to your tests. Otherwise, you won't be able to stub its methods without the TS compiler complaining about it. You also won't be able to write assertions using the params object, for similar reasons.
Since I don't use TS regularly, I'm not too familiar with it's common dependency injection techniques, but one thing you could do is add optional constructor arguments to your S3Uploader class that can overwrite the default s3 and arguments properties, like so:
constructor(s3, params) {
if (s3) this.s3 = s3;
if (params) this.params = params;
}
After which, you can create a stub instance and pass it to your test instance like this:
const s3 = sinon.createStubInstance(AWS.S3);
const params = { foo: 'bar' };
const uploader = new S3Uploader(s3, params);
Once you have the stub instance in place, you can write assertions to make sure the upload method was called the way you want it to be:
sinon.assert.calledOnce(s3.upload);
sinon.assert.calledWith(s3.upload, sinon.match.same(params), sinon.match.func);
You can also affect the behavior the upload method using the sinon stub api. For example, to make it fail like so:
s3.upload.callsArgWith(1, null);
Or make it succeed like so:
const data = { whatever: 'data', you: 'want' };
s3.upload.callsArgWith(1, null, data);
You'll probably want a completely separate test for each of these cases, using an instance before hook to avoid duplicating the common setup stuff. Testing for success will involve simply awaiting the promise and checking that its result is the data. Testing for failure will involve a try/catch that ensures the promise was rejected with the proper error.
Also, since you seem to be doing actual unit tests here, I'll recommend testing each S3Uploader method separately instead of calling them all in once big test. This drastically reduces the number of possible cases you need to cover, making your tests a lot more straightforward. Something like this:
#suite
class S3UploaderTest {
params: any; // Not sure the best way to type this.
s3: any; // Same. Sorry, not too experienced with TS.
uploader: S3Uploader | undefined;
before() {
this.params = {};
this.s3 = sinon.createStubInstance(AWS.S3);
this.uploader = new S3Uploader(this.s3, this.params);
}
#test
"send should set Body param and return instance"() {
const stream = "HalloWelt";
const result = this.uploader.send(stream);
Chai.expect(this.params.Body).to.equal(stream);
Chai.expect(result).to.equal(this.uploader);
}
#test
"toBucket should set Bucket param and return instance"() {
const bucket = "onetimeupload.test"
const result = this.uploader.toBucket(bucket);
Chai.expect(this.params.Bucket).to.equal(bucket);
Chai.expect(result).to.equal(this.uploader);
}
#test
"toFolder should set Key param and return instance"() {
const path = "onetimeupload.test"
const result = this.uploader.toFolder(path);
Chai.expect(this.params.Key).to.equal(path);
Chai.expect(result).to.equal(this.uploader);
}
#test
"upload should attempt upload to s3"() {
this.uploader.upload();
sinon.assert.calledOnce(this.s3.upload);
sinon.assert.calledWith(
this.s3.upload,
sinon.match.same(this.params),
sinon.match.func
);
}
#test
async "upload should resolve with response if successful"() {
const data = { foo: 'bar' };
s3.upload.callsArgWith(1, null, data);
const result = await this.uploader.upload();
Chai.expect(result).to.equal(data);
}
#test
async "upload should reject with error if not"() {
const error = new Error('Test Error');
s3.upload.callsArgWith(1, error, null);
try {
await this.uploader.upload();
throw new Error('Promise should have rejected.');
} catch(err) {
Chai.expect(err).to.equal(err);
}
}
}
If I were doing this with mocha proper, I'd group each method's tests into a nested describe block. I'm not sure if that's encouraged or even possible with mocha-typescript, but if so you might consider it.
I recently tried updating my project code structure with es6 classes in nodejs.
Here's what a controller class look like
class TaskController {
constructor(io, redisClient) {
this.socket = io;
this.redisClient = redisClient;
}
create(req, res) {
let taskDetails = req.body;
taskDetails.owner = req.user.id;
let errorFields = Validation.validate(taskDetails, [
{ name: 'title', type: 'text' }
]);
if (errorFields.length > 0) {
return ErrorHandler.handleError(res, errorFields);
}
...`
}
}
and Here's how I'm initializing the class in a routes file
module.exports = (app, io, redisClient) => {
let taskController = new TaskController(io, redisClient);
router.post('', middlewares.isAuthorized('task:create'), taskController.create);
app.use('/api/tasks', middlewares.isAuthenticated(), router);
};
The problem is while hitting the create api the this object is undefined in the create function.
With a little debugging, I know that the constructor is called when registering the routes.
But the taskController looses context when the create function is actually called.
How can I make it work with this structure or do I have to revert back to
module.exporting every function task controller?
You can use bind method to bind context. while passing create function.
router.post('', middlewares.isAuthorized('task:create'), taskController.create.bind(taskController));
create method is losing context because it is getting called in different scope by post method.
You can bind context using bind method which returns a function and pass that function to create method.