WhyTF am I putting my Node in The Intern?


  • I survived the hour long Uno hand

    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?


  • FoxDev

    Is there a need to use async()? This seems like something that should be run synchronous.


  • I survived the hour long Uno hand

    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.



  • @RaceProUK said:

    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.


  • I survived the hour long Uno hand

    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.)


  • FoxDev

    What's the dao you're using? I might be able to spin something up if I know what the function signatures are…



  • @mott555 said:

    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 survived the hour long Uno hand


  • ♿ (Parody)

    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:

    @Yamikuronue said:

    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 :'(


  • FoxDev

    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!


  • FoxDev

    @RaceProUK said:

    If that doesn't work… @accalia!

    i have been summone and i am puzzled.

    looking into it.


  • FoxDev

    @accalia said:

    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 ;)


  • I survived the hour long Uno hand

    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)


  • FoxDev

    @RaceProUK said:

    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.


  • I survived the hour long Uno hand

    @accalia said:

    try retruning deferred

    No change


  • FoxDev

    well that was worth a try.

    http://howtonode.org/promises

    looking at that, is defer itself a promise or does it contain a promise?



  • @Yamikuronue said:

    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 🔥


  • I survived the hour long Uno hand

    Contains a promise.


  • I survived the hour long Uno hand

    @cartman82 said:

    Does it wait 10 secs?

    Yes...

    @cartman82 said:

    See what happens.

    Same thing. Notably, neither of my other two tests in this suite run (both trivial cases) the minute I introduce deferred


  • I survived the hour long Uno hand

    @izzion said:

    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.


  • I survived the hour long Uno hand

    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?


  • I survived the hour long Uno hand

    If a callback is provided, it will be called immediately

    WHAT THE EVERLOVING FUCK IS THE POINT OF THE CALLBACK THEN

    brb having aneurysm.


  • FoxDev

    @Yamikuronue said:

    Contains a promise.

    unwrap the promise and return it? (return deferred.promise?)


  • I survived the hour long Uno hand

    @cartman82 said:

    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.


  • FoxDev

    @tarunik said:

    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?



  • @Yamikuronue said:

    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.


  • I survived the hour long Uno hand

    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.


  • I survived the hour long Uno hand

    @cartman82 said:

    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.


  • I survived the hour long Uno hand

    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.


  • FoxDev

    @Yamikuronue said:

    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!

  • I survived the hour long Uno hand

    @izzion said:

    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.


  • I survived the hour long Uno hand

    Fair enough.


  • I survived the hour long Uno hand

    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.


  • I survived the hour long Uno hand

    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.


  • FoxDev

    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?


  • I survived the hour long Uno hand

    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


  • I survived the hour long Uno hand

    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.


  • FoxDev

    @Yamikuronue said:

    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…


  • I survived the hour long Uno hand

    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.


  • I survived the hour long Uno hand

    @RaceProUK said:

    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


  • I survived the hour long Uno hand

    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);
    			}
    		});
    	},
    

Log in to reply