WhyTF am I putting my Node in The Intern?
-
So I've got a basic CRUD function backed by an sqlite database. I'm trying to do the most straightforward test I can think of: assert there are no rows, insert a row, assert there is now one row.
I'm using The Intern unit test runner... and Node.js, so I'm in callback hell. It's not waiting for my async test to complete.
The test:
insert: function () { dao.createDB("unitTest.db"); var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); var deferred = this.async(); dao.addItem("Test", "test", 0, deferred.callback( function() { var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); }) ); },
Output:
tests/intern Total: Pending Passed: 0 Failed: 0 Skipped: 0
If I take out the promises, I get the following:
✓ main - todoItemTests - createsDB ✓ main - todoItemTests - tests - insert ✓ main - todoItemTests - tests - errors tests/intern Total: [✓✓✓] 3/3 Passed: 3 Failed: 0 Skipped: 0
But the code coverage says my "retrieve items" code was not run, because there was always 0 items when getItems was called.
WTF am I doing wrong?
-
Is there a need to use
async()
? This seems like something that should be run synchronous.
-
HMM I WONDER.
Fuck Node. Everything's async by default. The sqlite library I'm using doesn't have blocking versions of the database functionality, so my test can't be sync either, from what I understand.
-
Is there a need to use async()? This seems like something that should be run synchronous.
Welcome to Node.js. Everything is async and you don't get a choice otherwise.
I'm not familiar with The Intern, I've used Nodeunit and that looks similar. In Nodeunit you have to call a function to tell the test runner that the test is completed, otherwise it assumes it's hung or failed.
-
Nodeunit is (from what I understand) forked from QUnit which we use on the front end, so I grok that a bit better. The purpose of this experiment is to try out The Intern to see if I recommend using it on our shiny new Node backend we're developing. I plan to later try out Mocha to see if that makes more sense, but my boss is intrigued by The Intern's one-stop-shop philosophy.
From The Intern's docs:
Asynchronous operations
Intern always uses Promise objects whenever an asynchronous operation needs to occur. All suite, test, and reporter functions can return a Promise, which will pause the test system until the Promise resolves (or until a timeout occurs, whichever happens first).But Promise objects are a whole nother mess of WTF (They're standard! No they're not! They're in the spec! They're out of the spec! There's 80,000 libraries that re-introduce them, all slightly different!), so I tried their next suggestion:
Calling this.async
All tests have a this.async method that can be used to retrieve a Deferred object. It has the following signature:this.async(timeout?: number, numCallsUntilResolution?: number): Deferred;
After calling this method, Intern will assume your test is asynchronous, even if you do not return a Promise. (If you do return a Promise, the returned Promise takes precedence over the one generated by this.async.)
-
What's the
dao
you're using? I might be able to spin something up if I know what the function signatures are…
-
Welcome to Node.js. Everything is async and you don't get a choice otherwise.
This is not Node's fault. It's the library she's using which implemented it using async (maybe because of Node, but IDK). IndexedDB is also async for example.
-
-
I'm not following the whole thing here (i.e., you have something called "insert" that doesn't seem to insert anything), but I'm pretty sure this isn't right:
assert.isTrue(items.length < 1,"Items had 1 or more items.");
Oh, nevermind...I write my messages with what I expected, not with the violation, so this confused me.
-
I took that test as being "if the
items
array that I just created has 1 or more things in it, throw an error that it had items in it after being initialized"Dammit, Hanzo'd by like 3 seconds :'(
-
Hmm… maybe the use of
deferred
is getting in the way?Try this:
insert: function () { dao.createDB("unitTest.db"); var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); dao.addItem("Test", "test", 0, function() { var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); }); },
If that doesn't work… @accalia!
-
-
i have been summone and i am puzzled.
I summoned you because you're more experienced with Node.js than me, and can tell me if I done messed up with my suggestion above ;)
-
That's what I had. The callback is not called before the test exits because it doesn't know to wait for it.
-
My guess is, you have to return deferred object, so your test runner will know when to end the tests.
insert: function () { dao.createDB("unitTest.db"); var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); var deferred = this.async(); dao.addItem("Test", "test", 0, deferred.callback( function() { var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); }) ); return deferred; },
(having never worked with this lib nor tested anything, so take it for what's it worth)
-
if I done messed up with my suggestion above
from a NodeJS standpoint yours is perfectly valid, no idea if it works with intern. because that may have different ideas.
@Yamikuronue try retruning deferred from your test function and see what happens. the docs do mention returning a promise and that might do it.
-
-
well that was worth a try.
looking at that, is defer itself a promise or does it contain a promise?
-
No change
Try adding timeout:
this.async(10000);
See what happens. Does it wait 10 secs?
-
And this is why Node.js needs to DIE IN A FIRE
-
Contains a promise.
-
Does it wait 10 secs?
Yes...
See what happens.
Same thing. Notably, neither of my other two tests in this suite run (both trivial cases) the minute I introduce deferred
-
it had items in it after being initialized"
Initialized and assigned to the representation of the complete set of rows in the brand new sqlite database, which ought to be 0 unless I miss my guess. I spent most of yesterday working out how to delete the DB between test runs.
-
Looking through the git code... Unless I'm missing something, this is wrong:
getItems: function(callback) { var exists = fs.existsSync(file); if (!exists || !db) this.createDB(file); var items = []; db.each("SELECT itemID,itemName,itemText,state FROM TodoItems", function(err, row) { Console.log(row); items.push({ id: row.itemID, name: row.itemName, text: row.itemText, state: !!row.state }); }, callback); return items; },
You can't return items synchronously. They will be filled only after callback is called.
Before doing anything else, I'd suggest setting up some kind of debugging environment. No point in trying to guess what's going on from the code alone.
-
Ah, I see the problem.
Your
dao.addItem
function...addItem: function(name, text, state, callback) { db.serialize(function() { var stmt = db.prepare("INSERT INTO TodoItems VALUES (?,?,?)"); stmt.run(name, text, state); stmt.finalize(); }, callback); },
You're expecting
db.serialize
to call your callback, but it won't. Look at the signature in the docs:It's not expecting callback. That's why your deferred is never resolved.
-
That makes sense, actually. I shoehorned in the callback last minute, I should call it with "items" as an argument instead.
But I can't find my own fucking bugs if I can't write my fucking tests now, can I?
-
If a callback is provided, it will be called immediately
WHAT THE EVERLOVING FUCK IS THE POINT OF THE CALLBACK THEN
brb having aneurysm.
-
-
That's why your deferred is never resolved.
It should actually be resolved immediately and the test should fail because there's no items. So that's still not expected behavior. The test should fail, not crash.
-
And this is why Node.js needs to DIE IN A FIRE
not so! but managers utter fascination with it for problems it was not designed to solve does!
also really with mocha and nunit so well established why would you go with anything else?
-
It should actually be resolved immediately and the test should fail because there's no items. So that's still not expected behavior. The test should fail, not crash.
You had two combinations:
- no timeout, no deferred returned
- timeout, deferred returned
Both combinations lead to nothing happening. From my brief look into this, it seems you should either give it a timeout or return deferred that resolves.
Or the lib is broken. They should have shown something when the thing timed out after 10 secs. Dunno.
-
This is the right answer:This works:insert: function () { dao.createDB("unitTest.db"); dao.getItems(function (items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); var deferred = this.async(10000); dao.addItem("Test", "test", 0, deferred.callback( function() { var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); }) ); }); },
Look at this. LOOK AT IT. Is this fucking sane? Is this worth the hassle? Is this code you'd be proud of? Fuck me.
-
You had two combinations:
One of them had the test erroneously passing. The other had the test never return. It should have been failing if I understand right, but I don't, clearly. Anyway it's fixed now.
-
That still doesn't look right, but at least you can start test / fix cycle now that some things are working. Good luck, callback style async is a bitch to get into.
-
Fuck me.
Neither of my callbacks were called from db.each.
If the result set succeeds but is empty, the callback is never called. In all other cases, the callback is called once for every retrieved row. The order of calls correspond exactly to the order of rows in the result set.
After all row callbacks were called, the completion callback will be called if present.
So the second get not being called would explain the first callback not running, but the second should always run.
-
Is this fucking sane? Is this worth the hassle? Is this code you'd be proud of?
No, not really, and fuck no. But then recently I fixed a bug by changing a variable name, so… yeah…
@Yamikuronue said:Fuck me.
Not my job; besides, @cloak15 might have something to say about it ;)
-
This post is deleted!
-
within the dao.addItem method, resolve the deferred
What? Why does the object under test know jack crap about what the test is testing for? The rest of my codebase isn't going to use The Intern's specific deferred object. It might use promises, but probably just callbacks.
-
Yeah, ok, strike that as me speaking before I thought it all the way through.
-
Fair enough.
-
Oh fuck.
var deferred = this.async(10000);
That's what's gone wrong.
Or rather, right. If that's never run, because callback failure, the test "passes". If it does run, it errors, because "this" is the wrong scope. If I move the object creation up a level, the whole suite bombs out again.
-
OK if I edit this code into the previous post it fucks up because Discourse but my test now looks like:
insert: function () { dao.createDB("unitTest.db"); var deferred = this.async(10000); dao.getItems(deferred.rejectOnError( function (items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); dao.addItem("Test", "test", 0, deferred.callback( function() { var items = dao.getItems(); assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); }) ); } )); },
And my output still never prints any tests run.
-
Hm, I read <a href=http://what.thedailywtf.com/t/wtf-am-i-doing-with-node-and-the-intern/9022/25?u=izzion>cartman's post to mean that the callback being passed to addItem isn't being executed, because of how db.serialize from the node-sqlite3 library is working.
-
insert: function () { dao.createDB("unitTest.db"); var deferred = this.async(10000); dao.getItems(deferred.rejectOnError( function (items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); dao.addItem("Test", "test", 0, deferred.callback( function() { dao.getItems(function (items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); }); }) ); } )); },
It seems logical to call
getItems()
with a callback both times?
-
Right, sorry, I also adjusted the DAO:
db.each("SELECT itemID,itemName,itemText,state FROM TodoItems", function(err, row) { Console.log(row); items.push({ id: row.itemID, name: row.itemName, text: row.itemText, state: !!row.state }); }, function() { callback(items); });
Github code is outdated now
-
You're right.
insert: function () { dao.createDB("unitTest.db"); var deferred = this.async(10000); dao.getItems(deferred.rejectOnError( function (items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); dao.addItem("Test", "test", 0, function() { dao.getItems( deferred.callback( function(items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); } ) ); }); } )); },
Still broken. Also I spent more time fixing braces and indents than I do writing code now.
-
Also I spent more time fixing braces and indents than I do writing code now.
If you have a decent editor, indents should handle themselves. Braces though…
-
Using
assert.isTrue(false,"This should fail if the callback was called.");
the callback from the first getItems call is being called.
the callback from addItem is not.
Manually adding
callback();
into addItem at various points, it fails if I place it after the following line:stmt.run(name, text, state);
So something's wrong with my prepared statement I think. I think it's bailing out silently.
-
If you have a decent editor
Sublime Text 3 keeps trying to help because it assumes I don't actually mean )})), that would be stupid, I obviously only need one of any given closing in a row.
Inserting a new nested function in the middle of what was procedural code fucks up the indenting in all editors I've tried, unless I explicitly tell it to reformat the file
-
Mother of christ.
Final answer:
insert: function () { dao.createDB("unitTest.db"); var deferred = this.async(10000); dao.getItems(deferred.rejectOnError( function (items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length < 1,"Items had 1 or more items."); dao.addItem("Test", "test", 0, function(err) { assert.isUndefined(err, "No error should occur."); dao.getItems( deferred.callback( function(items) { assert.isArray(items, "Items was not an array"); assert.isTrue(items.length === 1,"Items did not contain one item."); } ) ); }); } )); },
Against the following object:
getItems: function(callback) { var exists = fs.existsSync(file); if (!exists || !db) this.createDB(file); var items = []; db.each("SELECT itemID,itemName,itemText,state FROM TodoItems", function(err, row) { console.log(row); items.push({ id: row.itemID, name: row.itemName, text: row.itemText, state: !!row.state }); }, function() { callback(items); }); }, addItem: function(name, text, state, callback) { var stmt = db.prepare("INSERT INTO TodoItems (itemName, itemText, state) VALUES (?,?,?)"); stmt.run(name, text, state, function(err) { if (err) { console.log(err); callback(err); } else { stmt.finalize(callback); } }); },