mardi 31 mars 2015

Testing promises and sync functions that throw errors

I'm trying to build and test a function at the same time. Testing makes sense and I love it in theory, but when It comes down to it it always is a pain in the behind.


I have a function that takes a string and throws errors when something goes awry if all goes well it's going to return the original text argument and therefore a truthy value, if not it should be caught by the promise it's either in or itself as the promise.


This is the test / what I actually want to do (which doesn't work).



var main = require("./index.js")
var Promise = require("bluebird")
var mocha = require("mocha")
var chai = require("chai")
var chaiPromise = require("chai-as-promised")
chai.use(chaiPromise)

var shouldThrow = [
"random", // invalid non-flag
"--random", // invalid flag
"--random string", //invalid flag
"--wallpaper", // invalid flag w/ match
"--notify", // invalid flag w/ match
"wallpaper", // valid non-flag missing option(s) image
"wallpaper image.jpg" // invalid flag value
"wallpaper http://ift.tt/1G3L9LT", // invalid flag value
"wallpaper //cdn.shopify.com/s/files/1/0031/5352/t/28/assets/favicon.ico?12375621748379006621", // invalid flag value
"wallpaper http://ift.tt/1FecoQO", // invalid flag value
"wallpaper http://ift.tt/1G3L9LV", // invalid flag value
"wallpaper http://ift.tt/1Fecqs1", // invalid flag value
"wallpaper http://ift.tt/1G3L76O --queue", // invalid flag value
"wallpaper http://ift.tt/1G3L76O --queue "+moment().subtract(1, "month").format("YYYY-MM-DD-HH-mm"), // invalid flag value
"wallpaper http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YY-MM-DD-HH"), // invalid flag value
"wallpaper --image http://ift.tt/1G3L9LT", // invalid flag value not https
"wallpaper --image //cdn.shopify.com/s/files/1/0031/5352/t/28/assets/favicon.ico?12375621748379006621", // invalid flag no protocol
"wallpaper --image http://ift.tt/1FecoQO", // invalid flag value not https
"wallpaper --image http://ift.tt/1G3L9LV", // invalid flag value not valid image
"wallpaper --image http://ift.tt/1Fecqs1", // invalid flag image not found
"wallpaper --image http://ift.tt/1G3L76O --queue", // invalid subflag queue missing value
"wallpaper --image http://ift.tt/1G3L76O --queue "+moment().subtract(1, "month").format("YYYY-MM-DD-HH-mm"), // invalid subflag queue date value is past
"wallpaper --image http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YY-MM-DD-HH"), // invalid subflag queue date value format
"--wallpaper --image http://ift.tt/1G3L76O", //no action non-flag
"--wallpaper --image http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"), //no action non-flag
"notify", // valid non-flag missing option(s) message, open
'notify --message "Hello world"', // valid flag missing params open
'notify --open "https://www.holstee.com"', // valid flag missing params message
'notify --message "Hello world" --open "http://www.holstee.com"', // invalid subflag value `open` should be https
'notify --message "Hello world" --open "https://www.holstee.com" --queue', // invalid subflag queue missing value
'notify --message "Hello world" --open "https://www.holstee.com" --queue '+moment().subtract(1, "month").format("YYYY-MM-DD-HH-mm"), // invalid subflag queue date value is past
'notify --message "Hello world" --open "https://www.holstee.com" --queue '+moment().add(1, "month").format("YY-MM-DD-HH"), // invalid subflag queue date value format
'--notify --message "Hello world" --open "https://www.holstee.com"', //no action non-flag
'--notify --message "Hello world" --open "https://www.holstee.com --queue "'+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"), //no action non-flag
]

var shouldNotThrow = [
'notify --message "Hello world" --open "https://www.holstee.com"',
'notify --message "Hello world" --open "https://www.holstee.com --queue "'+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"),
"wallpaper --image http://ift.tt/1G3L76O",
"wallpaper --image http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"),
]

describe('Process Text', function(){
return Promise.map(shouldThrow, function(option){
it('throw error', function(){
return main.processText(option).should.throw()
})
})
return Promise.map(shouldNotThrow, function(option){
it('throw error', function(){
return main.processText(option).should.not.throw()
})
})
})


Here's a snapshot of the non-working* function I'm trying to test.



main.processText = function(text){
var args = minimist(text.split(" "))
var actions = _.keys(actionsFlags)
var flags = _.chain(_.map(actionsFlags, _.keys)).flatten().uniq().value()
var extraUnparsed = _.extra(actions, args._)
var providedFlags = _.chain(args).keys().without("_").value()
var extraParsed = _.extra(flags, providedFlags)
var validActions = _.intersection(actions, args._)
var requiredFlags = _.mapObject(actionsFlags, function(flags){
return _.filterObject(flags, function(flag){
return flag
})
})
if(extraUnparsed.length) throw new Error("invalid unparsed argument(s): "+extraUnparsed.join(", "))
if(extraParsed.length) throw new Error("invalid parsed argument(s): "+extraParsed.join(", "))
if(validActions.length > 1) throw new Error("too many actions: "+validActions.join(", "))
if(validActions.length == 0) throw new Error("no action: "+actions.join(", "))
_.each(actions, function(action){
var missingFlags = _.missing(_.keys(requiredFlags[action]), providedFlags)
var extraFlags = _.extra(_.keys(requiredFlags[action]), providedFlags)
if(_.contains(args._, action)){
if(missingFlags.length) throw new Error(util.format("missing required flags for %s: %s", action, missingFlags.join(", ")))
if(extraFlags.length) throw new Error(util.format("extra flags for %s: %s", action, extraFlags.join(", ")))
}
})
return text
}


Note its not a promise and doesn't return any promises yet. One of the validation features I want is to check a if a url responds in a 200 status code, that's gonna be a request promise. If I update this function then does all of the function contents need to be nested within a Promise.resolve(false).then()? Perhaps the promise shouldn't be in this block of code and all async validation operations should exist somewhere else?


I don't know what I'm doing and I'm a little frustrated. I'm of course looking for some golden bullet or whatever that will make sense of all this.


Ideally I could use some help on how to test this kind of function. If I make it into a promise later on I still want all my tests to work.




Here's some example code of what I mean by sync functions and promises.



function syncFunction(value){
if(!value) throw new Error("missing value")
return value
}

function asyncFunction(url){
return requestPromise(url)
}

// Both of these will throw errors the same way they will be caught by the promise then you can use `.catch` (in bluebird).

Promise.resolve(false).then(function(){
return syncFunction()
})

Promise.resolve(false).then(function(){
return asyncFunction("http://404.com")
})


I want this to reflect the way that I test for errors and whether something should or should not throw an error in my test.




I left the promises out of it, it's a sync function and I'm testing like this.



describe('Process Text', function(){
_.each(shouldThrow, function(option){
it('throw error ('+option+')', function(){
expect(function(){
main.textValidation(option)
}).to.throw()
})
})
_.each(shouldNotThrow, function(option){
it('not throw error ('+option+')', function(){
expect(function(){
main.textValidation(option)
}).to.not.throw()
})
})
})

Aucun commentaire:

Enregistrer un commentaire