I am working on creating a test library, and I found DI to be pretty convenient, maybe even to create more functional coding patterns.
Normally we can have an awkward before-hook setup in Node.js testing libraries, and it's always a little unclear when things actually get initialized and refactoring can be harder than it should be:
let client;
before(function(){
return client = ... // client requires asynchronous initalization
});
it('tests client', function(){
assert(client.isConnected); // client is declared / initialized...right?
});
I've always hated that pattern, sometimes just for ergonomic reasons. It just seems awkward to me. Because client
will be in scope in places where it is not yet initialized.
I thought about avoiding this pattern and finally devised a way around it, it basically looks like this:
describe('parent', function(){
inject(() => {
return {
client: new Client({port: 7033}).ensure() // returns a promise
}
});
describe('child', (client) => {
// client has been initialized, and is injected here
it('tests client', function(){
// ok we know for sure client is defined
assert(client.isConnected);
});
});
});
as you can see in any child block, we can inject parameters from the parent block.
This is simply a nicety, and makes things easier to grok and less error prone. Maybe even more functional programming style, not sure.
My real concern is supporting static typing, with TypeScript and allowing for at least some static analysis for correctness.
My question is - does anyone know if there is some ES7 or ES8 feature (maybe annotations/decorators?) that can somehow allow for some name checking or type checking in this kind of scenario?
I don't want to create some crazy feature that is so dynamic that it's hard to use.
Basically the way it works currently is that Promise.resolve is called for key/value returned from the inject hook, then this value is injected into the child blocks, by name. So the parameter to the child block callback function will have to match the key of the returned object from the inject hook. This is an API feature in progress and looking for constructive criticism.
To clarify, this would be an alternative API design option:
inject('client', () => {
return new Client({port: 7033}).ensure()
});
then we could use the string 'client' as the matching value to determine where to inject the client variable. Of course, you would only be able to return a single value from this, and not a tuple.
I am also looking for a good way to implement this with callbacks instead of promises. I am not going to reinvent the wheel, so somewhere along the way we should use the async library or similar, here is what I think is the best option:
inject.cb(j => {
async.parallel({a: foo(), b: bar()}, j);
});
describe('child', (a, b) => {
// a and b have been initialized, and are injected here
});
this way my library could work with any other library that fired a final callback with this signature:
function complete(err, results){}
where results is a simple key-value map; j would be the library callback which accepts the above arguments.
Another issue of course, is handling immutability, because the same object(s) will get injected into multiple places:
inject('client', () => {
return new Client({port: 7033}).ensure()
});
describe('child1', (client) => {
// client is the same object as the below child block
});
describe('child2', (client) => {
// client is the same object as the above child block
});
So should make client immutable somehow.
done
would be useful.before(function (done) { client.connect(function () { done(); }); }
\$\endgroup\$then
on the promise and pass the asynchronous completiondone
. Would that address the problem? \$\endgroup\$