Testing a constructor that contains asynchronous code
<p>I am trying to test a constructor in node.js that uses asynchronous code, to test out a feature in it. I know the asynchronous code works because I have already ran it, before I put in the feature, as if I were an end-user. <a href="https://stackoverflow.com/questions/38673768/functions-not-being-reached">In fact, I got it thanks to some user who answered another question I had</a></p>

<p><strong>The feature</strong></p>

<p>The <code>User</code> constructor has a <code>userRole</code> member, and some async code that checks <code>userType</code> specified against <code>User.userTypes</code>. If <code>userType</code> found, <code>this.userRole</code> is set to <code>userType</code>. Else, exception is thrown.</p>

<p>The code looks like this: </p>

<pre><code>async.forEachSeries(
    User.userTypes,
    function(user_type, callback)
    {
        if (user_type == userType)
        {
            this.userRole = user_type;
            return;
        }
        if (user_type == User.userTypes[User.userTypes.length - 1])
        {
            callback(helpers.invalidData("user_role"));
            return;
        }
        callback(null);
    },
    function(err)
    {
        if (err)
        {
            if (DEBUG)
            {
                console.log("Error from constructor...");
                console.log(JSON.stringify(err, null, '\t') + "\n");
            }
            throw err;
        }
    }
);
</code></pre>

<p>The rest of the constructor looks like:</p>

<pre><code>function User(id, email, displayName, password, userType, deleted, cb)
{
    var DEBUG = true;
    var error = null;
    this.userID = id;
    this.email = email;
    this.displayName = displayName;
    this.deleted = deleted;
    var self = this;
    async.forEachSeries(
        User.userTypes,
        function(user_type, callback)
        {
            if (user_type == userType)
            {
                this.userRole = user_type;
                return;
            }
            if (user_type == User.userTypes[User.userTypes.length - 1])
            {
                callback(helpers.invalidData("user_role"));
                return;
            }
            callback(null);
        },
        function(err)
        {
            if (err)
            {
                if (DEBUG)
                {
                    console.log("Error from constructor...");
                    console.log(JSON.stringify(err, null, '\t') + "\n");
                }
                throw err;
            }
        }
    );
    if (User.connectedToDatabase) this._password = password;
    else
    {
        bcrypt.genSalt(10, function (e, salt) {
            bcrypt.hash(password, salt, function (e, hash) {
                if (!e)
                {
                    self._password = hash;
                    if (DEBUG)
                    {
                        console.log("this._password ==" + self._password);
                        console.log("this.userID == " + self.userID);
                    }
                    if (typeof cb === 'function')
                        cb(null, this);

                }
                else
                {
                    console.log("Error occurred: ");
                    console.log(e);
                    if (typeof cb === 'function')
                        cb(e);
                }
            })
        });
    }
}

User.connectedToDatabase = false;
User.BASIC = "basic user";
User.INVENTORY_MANAGEMENT = "inventory";
User.ADMIN = "admin";
User.userTypes = [ User.BASIC, User.INVENTORY_MANAGEMENT, User.ADMIN ];

User.prototype.userID = 0;
User.prototype.email = null;
User.prototype.displayName = null;
User.prototype._password = null;
User.prototype.userRole = User.BASIC;
User.prototype.deleted = false;

User.prototype.responseObject = function() {
    return {
        id: this.userID,
        email: this.email,
        displayName: this.displayName,
        userType: this.userRole
    };
}
</code></pre>

<p><strong>My test</strong></p>

<p>I write <code>test()</code> function that takes parameters to pass to <code>User</code>. If <code>User</code> constructed without error, it, along with some of its members are printed to console. Else, error is printed. Here is code:</p>

<pre><code>function test(email, name, pass, type)
{
    try
    {
        var a = new User(Math.round(Math.random() * 32),
            email, 
            name, 
            pass, 
            type
        );
        console.log("Test user created: " + JSON.stringify(a.responseObject(), null, '\t') + "\n");
        console.log("User._password == " + a._password);
        console.log("\n");
    }
    catch (e)
    {
        console.log("User could not be created.\n" + JSON.stringify(e, null, '\t') + "\n");
    }
    /*async.waterfall([
        function(callback){
            var a = new User(Math.round(Math.random * 32),
                email,
                name,
                pass, 
                type);
            callback(null, a);
        },
        function(a, callback) {
            console.log("Test user created: " + JSON.stringify(a, null, '\t') + "\n");
            console.log("User._password == " + a._password);
            console.log("User.userID == " + a.userID);
            console.log("\n");
            callback(null, a);
        }
    ],
    function(err, results)
    {
        console.log("results of test: " + JSON.stringify(results, null, '\t'));
        if (err)
        {
            console.log("User could not be created.\n" + JSON.stringify(err, null, '\t') + "\n");

        }
    })*/

}
</code></pre>

<p>My test cases are as follows:</p>

<ol>
<li>userType matching first element of <code>User.userTypes</code></li>
<li>userType matching another element of <code>User.userTypes</code> (I picked the last one)</li>
<li>userType not matching any of <code>User.userTypes</code></li>
</ol>

<p>At runtime, after new User created, <code>User._password</code> is default value and not the value my asynchronous code comes up with for it.<a href="https://i.stack.imgur.com/MeoNP.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/MeoNP.png" alt="Sample run"></a> I suspect I have some async-sync error, but haven't been able to fix it.</p>