Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Unit Testing with Jasmine and Async/Await

DZone's Guide to

Unit Testing with Jasmine and Async/Await

In this article, you'll learn how to conduct asynchronous unit testing on your code using async/await, Jasmine, and NodeJS technologies.

· Web Dev Zone
Free Resource

Make the transition to Node.js if you are a Java, PHP, Rails or .NET developer with these resources to help jumpstart your Node.js knowledge plus pick up some development tips.  Brought to you in partnership with IBM.

Today, I wanted to write some unit tests against a Firebase database using Jasmine.

Connecting to Firebase from Node is just as easy as it is from a browser. I’ve done it before on a previous version of the Firebase SDK, so I didn’t expect a lot of problems there.

It was really just the butt-ugly tests I was worried about.

Testing asynchronous operations with Jasmine has always been supported; originally with the runs() and waitsFor() methods, which made for somewhat verbose tests, and later in 2.0 with done(), which is, IMHO, a clumsy promise + callback-to-move-on hack.

So, I decided to try a different approach. While async/await didn’t make it into ECMAScript 2016 (ES7), it is available in Chrome’s V8 as an experimental feature, and is supported in Node 7.6 without special flags. I’d been hearing a lot about it so I decided to give it a whirl. Spoiler alert: It’s totally frickin’ awesome!

Even a Simple Task Can Sometimes Be Made Simpler

Making asynchronous code look synchronous is a real trick, regardless of what language you’re trying to do it with. JavaScript Promises are a big help, but async/await comes closer than anything I’ve seen so far.

The basic premise looks like this:

async function fetchOrSupplyDefault(url) {
  let retval;
  try {
    retval = await fetch(url); 
  } catch(e) {
    retval = "Default Data";
  }
  return retval;
}

In the above function, we use the async keyword to indicate that the function will be performing one or more asynchronous operations. The  fetch() function returns a promise, so we would normally chain a then() call, passing a function to be called when the promise resolves, making things much more unreadable. Instead, we can prepend the await keyword, and when the promise resolves, the waiting variable retval will be set.

One thing to note here is that return from an async function will be wrapped in Promise.resolve and should be handled accordingly. Still, that’s already pretty slick. You couldn’t ask for more readable code inside this function.

Now Let’s Try That in a Test

The nice thing about using async/await inside Jasmine is that a try/catch already surrounds each test so that the framework can tally all the errors in the suite rather than crashing to a halt the first time it encounters one. That makes our code even simpler.

Note: Tip o' the propeller beanie to Joseph Zimmerman for pointing out in the comments that my original database test was flawed. The upshot was that I needed to add the jasmine-await npm package. For additional proof I've written the fetched data snapshot's uid property to stdout.

In the following test suite, we:

  1. Use the jasmine-await library. (It extends the functions it()beforeEach()afterEach()beforeAll() and afterAll()and wraps them in the async() function. So you can always use await() to wait for a promise's resolution or rejection.)
  2. Use the firebase-admin node library to connect to a database using the serviceAccount.json file that can be downloaded from the Firebase console for the project. This happens in the call to beforeAll(), so that it is done once prior to running the tests. Straightforward stuff. Handy if you need to understand how to connect to Firebase, otherwise not much to linger on here.

  3. Test that the app was is initialized successfully. A basic, synchronous test.

  4. Test that async / await actually works with a simple example from the Mozilla docs.

  5. Test that a known user profile can be downloaded from Firebase. This is where the magic happens.

// Use the jasmine-await npm package
var async = require("jasmine-await");
var it = async.it;
var await = async.await;

describe("Test Firebase access with async/await", () => {
    let admin = require("firebase-admin");
    let serviceAccount = require(__dirname + "/serviceAccountKey.json");
    let app = null;

    // Initialize the app with loaded credentials
    beforeAll( () => {
        app = admin.initializeApp({
            credential: admin.credential.cert(serviceAccount),
            databaseURL: "https://mytestdatabse.firebaseio.com"
        });
    });

    // Test 1: Ensure we've got an initialized app
    it("receives an initialized app from firebase", () => expect(app).not.toBe(null) );

    // Test 2: Prove that async/await works
    it('tests that async / await works', async () => {
        function resolveAfter2Seconds(x) {
            return new Promise(resolve => {
                setTimeout(() => {
                    resolve(x);
                }, 2000);
            });
        }

        async function add1(x) {
            var a = resolveAfter2Seconds(20);
            var b = resolveAfter2Seconds(30);
            return x + await a + await b;
        }

        var v = await add1(10);
        expect(v).toBe(60);
    });

    // Test 3: Read a known profile
    it("fetches a known profile from firebase", async () => {
        const uid = "1sT1mpFabkVJcXHtTCTsqiLiTrF3";
        let profileRef = admin.database().ref('/profile/' + uid);
        let snapshot = await profileRef.once('value');
        expect(snapshot).not.toBe(null);
        expect(snapshot.val().uid).toBe(uid);
        process.stdout.write(""+snapshot.val().uid); // For visual proof
    });
});

Running this test suite outputs:

Destiny:SineWav3.Client.Shell cliff$ jasmine
Started
..1sT1mpFabkVJcXHtTCTsqiLiTrF3.


3 specs, 0 failures
Finished in 3.034 seconds

Whoa! That Really Worked? Lets Break It to be Sure.

It was almost too easy. In order to convince myself that async/await was having the intended effect and that the magic wasn’t wrapped up in the Firebase Admin SDK, I removed the async and await keywords from the final test:

    // Test 3: Read a known profile
    it("fetches a known profile from firebase", () => {
        const uid = "1sT1mpFabkVJcXHtTCTsqiLiTrF3";
        let profileRef = admin.database().ref('/profile/' + uid);
        let snapshot = profileRef.once('value');
        expect(snapshot).not.toBe(null);
        expect(snapshot.val().uid).toBe(uid);
        process.stdout.write(""+snapshot.val().uid);
    });

Now the output looks like:

Destiny:SineWav3.Client.Shell cliff$ jasmine
Started
..F

Failures:
1) Test Firebase with async/await fetches a known profile from firebase
  Message:
    Failed: snapshot.val is not a function
  Stack:
    TypeError: snapshot.val is not a function
        at it (/Users/cliff/Documents/SineWav3.Client.Shell/spec/db-project-spec.js:49:25)
        at tryBlock (/Users/cliff/Documents/SineWav3.Client.Shell/node_modules/asyncawait/src/async/fiberManager.js:39:33)
        at runInFiber (/Users/cliff/Documents/SineWav3.Client.Shell/node_modules/asyncawait/src/async/fiberManager.js:26:9)

3 specs, 1 failure
Finished in 2.08 seconds

Conclusion

I am generally slow to get onboard with additions to JavaScript since I’d rather allow browsers to widely adopt first. But in the case of Node.js, I only have one JS engine to be concerned about. As long as I’m running a stable version of Node that supports a feature, I’m happy to consider it. In this case, I’m really glad I did, and I highly recommend you give it a try in your own scripts or unit tests. But keep in mind, it’s not something you want to fold into your production browser-based code just yet, since it’s still experimental, at least until ES8.

Learn why developers are gravitating towards Node and its ability to retain and leverage the skills of JavaScript developers and the ability to deliver projects faster than other languages can.  Brought to you in partnership with IBM.

Topics:
javascript ,node js ,web dev ,asynchronous testing

Published at DZone with permission of Cliff Hall. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}