I'm trying to test the following "worker" method in a Class without success as it is "an infinite loop"/recursive:
Class TheClassYaDaYaDa {
constructor(var1) {
(...)
}
async worker() {
return new Promise(async (resolve, reject) => {
try {
await this.doStuff();
return resolve();
} catch (err) {
return reject(err);
} finally {
this.worker();
}
});
}
}
And this is the test I'm building:
it('It should not throw error', async function () {
let error = false;
const var1 = 'parameterTest';
stub1 = sinon.stub(TheClassYaDaYaDa.prototype, 'doStuff').resolves();
// if I use the following stub logically the test will not succeed. If I don't I get an infinte loop
// stub2 = sinon.stub(TheClassYaDaYaDa.prototype, 'worker').resolves();
let classToTest;
try {
classToTest = new TheClassYaDaYaDa(limitedInstance);
result = await classToTest.worker();
} catch (err) {
error = err;
}
expect(error).to.be.false;
sinon.assert.calledOnce(stub1);
//also assert that the finally statement run at least once!
});
Is there a way to test this scenario?
I would approach this from a different angle, recursion is always tricky but one of the popular designs is to split the scheduling and running logic into two.
Consider the following:
async worker() {
await this.scheduleWorker();
}
private scheduleWorker() {
// return new Promise ...
}
It's now possible to test that the scheduleWorker function calls worker, and also that worker calls scheduleWorker (if private methods are accessible).
If not, you could mock the worker method to only return the worker logic on the first call and noops on subsequent calls. This would also work for your current design allowing you to assert the recursive call was correctly triggered.
I am not well versed on sinon but you want something similar to:
var originalWorker = classToTest.originalWorker;
classToTest.originalWorker = function() {
originalWorker();
classToTest.originalWorker = function() { }
}
Most mocking frameworks have similar controls to enable mocking methods multiple times (for subsequent calls).
Related
I hope someone can give me some advice
I have created a module, that basically creates a redis connection using a singleton pattern which also uses promises.
However, before I return the redis connection, I first check if the connection is ready, on the ready event, I resolve the promise and likewise on any errors, I reject the promise.
My only concern is with this method, can I introduce memory leaks as the listeners function on ready and error may continue listening well after the promise has completed and should be cleaned up by the garbage collector.
I am not sure if this will create some kind of memory leaks..
Any advice would be much appreciated.
import redis from 'redis';
import redisearch from 'redis-redisearch';
redisearch(redis);
let redisClient: redis.RedisClient = null;
export function getRedisClient(): Promise {
return new Promise((resolve: any, reject: any) => {
if (redisClient && redisClient.connected) {
return resolve(redisClient);
}
redisClient = redis.createClient({
password: process.env.REDIS_PASSWORD,
retry_strategy: function (options) {
if (options.error && options.error.code === "ECONNREFUSED") {
// End reconnecting on a specific error and flush all commands with
// a individual error
return new Error("The server refused the connection");
}
if (options.total_retry_time > 1000 * 60 * 60) {
// End reconnecting after a specific timeout and flush all commands
// with a individual error
return new Error("Retry time exhausted");
}
if (options.attempt > 10) {
// End reconnecting with built in error
return undefined;
}
// reconnect after
return Math.min(options.attempt * 100, 3000);
},
});
redisClient.on("ready", function (error: any) {
console.log("connection is good");
return resolve(redisClient);
});
redisClient.on("error", function (error: any) {
console.log("reject error");
if (redisClient) {
redisClient.end(false);
redisClient = null;
return reject(error);
}
});
})
}
This pattern can create multiple redisClients if getRedisClient is called multiple times before any of the redisClients finish connecting. A slightly simpler pattern might be to cache a Promise<RedisClient> rather than the redisClient:
let redisClientP: Promise<RedisClient>;
function getRedisClient (): Promise<RedisClient> {
if (redisClientP) return redisClientP;
redisClientP = new Promise(...previous code)
return redisClientP;
}
If you haven't seen this type of cached Promise usage before, here's a small snippet demonstrating that you can access the .then on a Promise multiple times.
const p = new Promise((resolve) => resolve(2));
(async function main () {
for (let i = 0; i < 100; i++) {
console.log(i, await p);
}
})()
I've found somewhere implementation of the mutex pattern in JavaScript that works well but it looks really complex. Even, though I understand mutex pattern I don't know why promises in some places are being used in a given way. But about that in a moment. I was playing around with it and I've created my own implementation that is much easier to understand and has less code. Here is project: https://codesandbox.io/s/mutex-2e014
You have there 3 implementations of the mutex pattern. Mutex 0 and 1 is the same version with this difference, that in the version 1, I've simplified code by taking advantage of arrow functions and being able to access this from the parent context. So overall they are the same solutions. My solution is Mutex 2.
You can test code by opening console and clicking buttons from 0 to 2. You can see that each asynchronous action takes 1 second and it will always execute operations in proper order and wait for the previous task to finish. It works the same way in all versions. Also when you hold the SHIFT key, when pressing buttons it will throw an exception and that part is also working the same in all versions. Next thing, I had to compare is return value and that's not different. So my questions is why is it so complex in the first 2 solutions? Am I missing something? Does it really work the same way?
Now the question about the promise usage that I don't understand. In the Mutex1.ts file in line 7, we have return new Promise(resolve);. We’re passing resolve from the parent promise as the first argument of the Promise constructor which is executor. And from what I read executors are being invoked just before promise creation. In this case, thanks to that we have access to the unlock function in the run method but I don’t know how it’s possible that this.lock returns function. Probably, I don't know promises well enough. Any ideas?
Mutex 0
export default class MutexA {
private mutex = Promise.resolve();
lock() {
let begin = unlock => {};
this.mutex = this.mutex.then(() => {
return new Promise(begin);
});
return new Promise(res => {
begin = res;
});
}
async run(fn) {
const unlock = await this.lock();
try {
return await Promise.resolve(fn());
} finally {
unlock();
}
}
}
Mutex 1
export default class MutexB {
private mutex = Promise.resolve();
lock() {
return new Promise(resolve => {
this.mutex = this.mutex.then(() => {
return new Promise(resolve);
});
});
}
async run(fn) {
const unlock = await this.lock();
try {
return await fn();
} finally {
unlock();
}
}
}
Mutex 2
export default class MutexC {
private mutex = Promise.resolve();
async run(fn) {
return new Promise((resolve, reject) => {
this.mutex = this.mutex.then(async () => {
try {
resolve(await fn());
} catch (err) {
reject(err);
}
});
});
}
}
I have a custom connect function that creates a promise I want resolved once I make a websocket call and receive an acknowledgement. The remote server may be up, it may be down, but if it's unavailable I want to keep trying until I'm successful.
const socketIOClient = require('socket.io-client');
function createTimeoutCallback(callback)
{
let called = false;
let timerID = setTimeout(() => {
if (called) return;
called = true;
callback(new TimeoutError());
},
60*1000);
return function() {
if (called) return;
called = true;
clearTimeout(timerID);
callback.apply(this, arguments);
}
}
async function myConnect()
{
let mysocket = socketIOClient(url);
return new Promise((resolve, reject) => {
mysocket.emit('clientconnect', args, createTimeoutCallback((resp) => {
if (!(resp instanceof TimeoutError)) {
// SUCCESS
doSomething();
resolve();
}
// We timed out, try again
else {
mysocket.close();
setTimeout(myConnect, 60*1000);
}
}));
});
}
await connect();
// doSomething() gets called but we never get here
In the above code, if the endpoint is available, everything works fine. But I'm never returning from the myConnect() function when (1) I wait on its promise; and (2) the function needs to make several connection attempts (e.g., the server is not initially up); and (3) the endpoint finally comes back online and the connection succeeds. I suspect this has everything to do with me essentially abandoning the original promise on a reconnect attempt, but I don't want to reject it or the operation will prematurely fail.
I did find a workaround, which relies on an embedded function. With this technique there is only one promise which is always accessible and in scope, and the timer-based recursion (i.e., not stack-based) is always wrapped in that single promise that is never abandoned. I put this together before Jaromanda answered, so I can't confirm that his solution would work.
async function myConnect()
{
return new Promise((resolve, reject) => {
function innerConnect()
{
let mysocket = socketIOClient(url);
mysocket.emit('clientconnect', args, createTimeoutCallback((resp) => {
if (!(resp instanceof TimeoutError)) {
// SUCCESS
doSomething();
resolve();
}
// We timed out, try again
else {
mysocket.close();
setTimeout(innerConnect, 60*1000);
}
}));
}
innerConnect();
});
}
I have a node.js app using express 4 and this is my controller:
var service = require('./category.service');
module.exports = {
findAll: (request, response) => {
service.findAll().then((categories) => {
response.status(200).send(categories);
}, (error) => {
response.status(error.statusCode || 500).json(error);
});
}
};
It calls my service which returns a promise. Everything works but I am having trouble when trying to unit test it.
Basically, I would like to make sure that based on what my service returns, I flush the response with the right status code and body.
So with mocha and sinon it looks something like:
it('Should call service to find all the categories', (done) => {
// Arrange
var expectedCategories = ['foo', 'bar'];
var findAllStub = sandbox.stub(service, 'findAll');
findAllStub.resolves(expectedCategories);
var response = {
status: () => { return response; },
send: () => {}
};
sandbox.spy(response, 'status');
sandbox.spy(response, 'send');
// Act
controller.findAll({}, response);
// Assert
expect(findAllStub.called).to.be.ok;
expect(findAllStub.callCount).to.equal(1);
expect(response.status).to.be.calledWith(200); // not working
expect(response.send).to.be.called; // not working
done();
});
I have tested my similar scenarios when the function I am testing returns itself a promise since I can hook my assertions in the then.
I also have tried to wrap controller.findAll with a Promise and resolve it from the response.send but it didn't work neither.
You should move your assert section into the res.send method to make sure all async tasks are done before the assertions:
var response = {
status: () => { return response; },
send: () => {
try {
// Assert
expect(findAllStub.called).to.be.ok;
expect(findAllStub.callCount).to.equal(1);
expect(response.status).to.be.calledWith(200); // not working
// expect(response.send).to.be.called; // not needed anymore
done();
} catch (err) {
done(err);
}
},
};
The idea here is to have the promise which service.findAll() returns accessible inside the test's code without calling the service. As far as I can see sinon-as-promised which you probably use does not allow to do so. So I just used a native Promise (hope your node version is not too old for it).
const aPromise = Promise.resolve(expectedCategories);
var findAllStub = sandbox.stub(service, 'findAll');
findAllStub.returns(aPromise);
// response = { .... }
controller.findAll({}, response);
aPromise.then(() => {
expect(response.status).to.be.calledWith(200);
expect(response.send).to.be.called;
});
When code is difficult to test it can indicate that there could be different design possibilities to explore, which promote easy testing. What jumps out is that service is enclosed in your module, and the dependency is not exposed at all. I feel like the goal shouldn't be to find a way to test your code AS IS but to find an optimal design.
IMO The goal is to find a way to expose service so that your test can provide a stubbed implementation, so that the logic of findAll can be tested in isolation, synchronously.
One way to do this is to use a library like mockery or rewire. Both are fairly easy to use, (in my experience mockery starts to degrade and become very difficult to maintain as your test suite and number of modules grow) They would allow you to patch the var service = require('./category.service'); by providing your own service object with its own findAll defined.
Another way is to rearchitect your code to expose the service to the caller, in some way. This would allow your caller (the unit test) to provide its own service stub.
One easy way to do this would be to export a function contstructor instead of an object.
module.exports = (userService) => {
// default to the required service
this.service = userService || service;
this.findAll = (request, response) => {
this.service.findAll().then((categories) => {
response.status(200).send(categories);
}, (error) => {
response.status(error.statusCode || 500).json(error);
});
}
};
var ServiceConstructor = require('yourmodule');
var service = new ServiceConstructor();
Now the test can create a stub for service and provide it to the ServiceConstructor to exercise the findAll method. Removing the need for an asynchronous test altogether.
I know of this question, but my need is a little different.
I'm trying to download multiple images but cannot prevent the app from exiting before the process is complete. I've gone through quite a few iterations, this is where I'm at currently:
router
.post('/', koaBody(), function *() {
var images = [];
//console.log(this.request.body.fields);
if (this.request.body.fields) {
images = this.request.body.fields['images'];
imageCount = images.length;
} else {
this.status = 500;
return;
}
var url = 'https://www.google.ca/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png';
var filepath = '../images/image_1.png';
var pipeRequest = function (url, filepath) {
return new Promise(function (resolve, reject) {
request.get(url)
.pipe(fs.createWriteStream(filepath))
.on('finish', function () {
console.log('piped');
});
});
};
co(function*() {
yield pipeRequest(url, filepath);
}).then(function () {
console.log('done co-func');
}).catch(function (err) {
return console.error(err);
});
console.log('App exiting...');
});
It's not the most elegant code but it works, if I wrap the co-function in a loop I can download as many as I want (the end-goal is to have this API receive a JSON list of image URLs). What I cannot do however is return the results of the piping to the caller, the app will always exit before that process is complete. I thought wrapping the function call in a yield would be the answer...but no.
When you're inside a generator function (a Koa handler), you use yield to wait.
The general pattern is to extract your async logic into a function that returns a promise, and then you yield it inside your route.
The first problem with your code is that you never use reject and resolve in the promise to transition it to a finished/errored state. The promise never knows when it's finished. Yielding it will never return control back to the route, which is probably why you nested it in another co process.
Here, I fix it so that it resolve()s on completion and reject(err)s on error. Note: yielding a promise that hits a reject(err) path will throw an exception (err) that you can try/catch at the call site.
var pipeRequest = function(url, filepath) {
return new Promise(function(resolve, reject) {
request.get(url)
.pipe(fs.createWriteStream(filepath))
.on('finish', function() { resolve() })
.on('error', function(err) { reject(err) })
});
};
The second problem I see is that you're nesting a co process in your route which is going to ensure that the route never waits on the yield inside it.
A Koa handler is already run inside a co process. Just replace that whole co block with a top-level yield pipeRequest(url, filepath) once you've implemented my resolve/reject changes above.