4
\$\begingroup\$

I'm upgrading my project from YouTube's V2 API to their V3 API. I'm also taking the time to upgrade to Jasmine 2.0 since I already need to look at my test cases.

Here's a couple of test cases and the corresponding code. Am I doing 'it' correctly? Maybe I should have a spy?

define([
    'common/model/youTubeV3API'
], function (YouTubeV3API) {
    'use strict';

    describe('YouTubeV3API', function () {

        describe('when asked to find a playable song by title', function () {
            beforeEach(function (done) {

                this.result = null;

                YouTubeV3API.findPlayableByTitle({
                    title: 'Gramatik',
                    success: function (response) {
                        this.result = response;
                        done();
                    }.bind(this)
                });
            });

            it('should return a playable result', function() {
                expect(this.result).not.toBeNull();
            });

        });

        describe('when asked to search for songs', function () {

            beforeEach(function (done) {

                this.result = null;

                YouTubeV3API.search({
                    text: 'Gramatik',
                    success: function (response) {
                        this.result = response;
                        done();
                    }.bind(this)
                });
            });

            it('should return 50 songs', function () {
                expect(this.result.length).toEqual(50);
            });

        });
    }
}

define([
    'common/model/utility',
    'common/googleAPI'
], function (Utility, GoogleAPI) {
    'use strict';

    var YouTubeV3API = Backbone.Model.extend({

        defaults: {
            loaded: false
        },

        initialize: function () {
            GoogleAPI.client.load('youtube', 'v3', function () {
                this.set('loaded', true);
            }.bind(this));
        },

        //  Performs a search and then grabs the first item most related to the search title by calculating
        //  the levenshtein distance between all the possibilities and returning the result with the lowest distance.
        //  Expects options: { title: string, success: function, error: function }
        findPlayableByTitle: function (options) {
            return this.search({
                text: title,
                success: function (songInformationList) {

                    songInformationList.sort(function (a, b) {
                        return Utility.getLevenshteinDistance(a.title, title) - Utility.getLevenshteinDistance(b.title, title);
                    });

                    var songInformation = songInformationList.length > 0 ? songInformationList[0] : null;
                    options.success(songInformation);
                },
                error: options.error,
                complete: options.complete
            });
        },

        //  Performs a search of YouTube with the provided text and returns a list of playable songs (<= max-results)
        //  Expects options: { maxResults: integer, text: string, fields: string, success: function, error: function }
        search: function (options) {
            //  If the API has not loaded yet  - defer calling this event until ready.
            if (!this.get('loaded')) {
                this.once('change:loaded', function () {
                    this.search(options);
                });
                return;
            }

            var searchListRequest = GoogleAPI.client.youtube.search.list({
                part: 'id',
                //  Probably set this to its default of video/playlist/channel at some point.
                type: 'video',
                maxResults: options.maxResults || 50,
                q: options.text,
                //  I don't think it's a good idea to filter out results based on safeSearch for music.
                safeSearch: 'none'
            });

            searchListRequest.execute(function (response) {
                if (response.error) {
                    if (options.error) {
                        options.error({
                            error: response.error
                        });
                    }

                    if (options.complete) {
                        options.complete();
                    }
                } else {
                    var songIds = _.map(response.items, function (item) {
                        return item.id.videoId;
                    });

                    this._getSongInformationList(songIds, function (songInformationList) {
                        options.success(songInformationList);

                        if (options.complete) {
                            options.complete();
                        }
                    });
                }

            }.bind(this));

        },
        //  Converts a list of YouTube song ids into actual video information by querying YouTube with the list of ids.
        _getSongInformationList: function(songIds, callback) {
            //  Now I need to take these songIds and get their information.
            var songsListRequest = GoogleAPI.client.youtube.videos.list({
                part: 'contentDetails,snippet',
                maxResults: 50,
                id: songIds.join(',')
            });

            songsListRequest.execute(function (response) {

                var songInformationList = _.map(response.items, function (item) {

                    return {
                        id: item.id,
                        duration: Utility.iso8061DurationToSeconds(item.contentDetails.duration),
                        title: item.snippet.title,
                        author: item.snippet.channelTitle
                    };

                });

                callback(songInformationList);
            });
        }
    });

    return new YouTubeV3API();
});

My main concerns are:

  • I'm really interested in knowing I am calling YouTube's API properly, but I think it's best practice to stub out the calls to YouTube and mock responses. Is that true?
  • I'm not using a Spy to check success value, but, again, seems a bit awkward to actually use it in my scenario... Would love an example.
  • Jasmine 2.0 syntax reads nicer, but seems a bit less generous with asynchronous calls. Have I structured things properly by placing code in a beforeEach, but only have one "it" call? I don't think I want to hammer YouTube's API many times for each "it" following a "beforeEach" ... so it seems odd to have it there. This problem would go away with stubbing my calls to YouTube though..
\$\endgroup\$
1
\$\begingroup\$

This looks okay to me, the only thing I will comment on is this:

            if (response.error) {
                if (options.error) {
                    options.error({
                        error: response.error
                    });
                }

                if (options.complete) {
                    options.complete();
                }
            }

If you default .complete() and .error() like this:

    var doNothing = function(){};
    options.complete = options.complete || doNothing();
    options.error = options.error || doNothing();

then that first block would be simply

            if (response.error) {
                options.error({ error: response.error });
                options.complete();
            }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.