简体   繁体   中英

Issue with unit testing a viewmodel in Knockout.js

I'm building a mobile app using PhoneGap and Knockout.js, and I'm using QUnit for unit testing it. I've run into a little issue.

It's essentially a photo gallery, and as part of one of the tests, I want to first get all the albums, which are loaded via AJAX. I then want to get all the photos in the first album returned.

I can get the albums alone, and have a working unit test for that functionality, but I'm having a lot of difficulty extending it to fetch the photos as well.

My viewmodel is called AlbumViewModel, and it has two observable arrays called albums() and photos(). When the application is first opened, the albums are fetched via AJAX automatically and albums() is populated with the results. When an album is selected, it's passed through to getPhotos(), which nullifies albums() and fetches the photos in the album, storing them in photos().

The unit test for the albums is as follows:

test("Check albums created", function () {
    'use strict';

    // Stop the test
    stop();

    // Declare variables used
    var avmodel;

    // Create AlbumViewModel
    avmodel = new AlbumViewModel();

    // Wait for album fetch
    avmodel.albums.subscribe(function () {
        // Check albums length is more than 0
        ok(avmodel.albums().length > 0, "Albums length is " + avmodel.albums().length);

        // Run the tests for the albums
        start();
    }); 
}); 

As this fetches data from the server via AJAX, I needed to delay execution until such time as the albums() observable array has been populated.

This works absolutely fine. However, the test for fetching the photos is being more problematic. I basically need to fetch the albums first of all, then, once they have been fetched, get one of them and fetch the photos. Now, as you can see above, I accomplished this with the albums by just subscribing to the albums.subscribe event and running the test once that was completed. So, that seemed like the way to go for testing the photos() are created.

Here is what I wrote for testing the photos:

test("Check photos created", function () {
    'use strict';

    // Stop the test
    stop();

    // Declare variables used
    avmodel;

    // Create AlbumViewModel
    avmodel = new AlbumViewModel();

    // Wait for album fetch
    avmodel.albums.subscribe(function () {

        // Only check the first time it runs
        var albumToGet;
        if(typeof albumToGet === "undefined") {
            // Get the album to examine
            if (avmodel.albums()) {
                albumToGet = avmodel.albums()[0];

                // Subscribe to the photos observableArray
                avmodel.photos.subscribe(function () {
                    var photos = avmodel.photos(albumToGet);

                    // Check photos length more than 0
                    ok(photos.length > 0, "Photos length is " + photos);

                    // Restart
                    start();
                }); 

                // Get the photos for this album
                avmodel.getPhotos(albumToGet);
            }   
        }   
    }); 
}); 

Essentially, I need to first of all delay getting the album details until such time as the AJAX response has been received and processed, and then, once that has been done, set the albumToGet object to be the first album and pass it to the getPhotos method, then delay execution of the tests until the AJAX response for fetching the photos has been received and processed.

I thought I could subscribe to the photos() observable array inside the subscription to the albums() one, but unfortunately Chrome's developer tools are returning the following error:

Uncaught RangeError: Maximum call stack size exceeded

That would seem to suggest an infinite loop, possibly meaning nesting subscriptions may not be the best approach. Can anyone point out where I've gone astray? I feel a bit out of my depth as this is my first time using both QUnit and Knockout.js.

TL;DR: I'm unit testing a Knockout.js application with QUnit. Need to wait for one method to work via AJAX, then get the data returned and pass it to a second method which also works via AJAX, in order to test the code. Subscribing to the first method worked for just testing that, but can't nest a second subscription inside the first. Any suggestions as to how I could do this?

The infinite loop you are getting is not because of the nested subscription.

The error is caused by this line at the beginning of the inner subscription

var photos = avmodel.photos(albumToGet);

Instead you should have

var photos = avmodel.photos();

You are essentially passing your custom album object into the observable array and that is what is causing the exception.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM